diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-10-30 17:25:37 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-10-30 17:25:37 -0400 |
commit | 46c77c203439b3b37c99d09e326df4b1fe08c10b (patch) | |
tree | 70d29abbfbb1106cd0830b33bc7e69e6fb151b1e | |
parent | 47ed54e5d312f899507d28d6e95ccc18a0de19fe (diff) | |
download | android-28-46c77c203439b3b37c99d09e326df4b1fe08c10b.tar.gz |
Import Android SDK Platform P [4423826]
/google/data/ro/projects/android/fetch_artifact \
--bid 4423826 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4423826.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: I45f7bdc9b9c1cdcba75386623ae5f3ead6db4da8
314 files changed, 19878 insertions, 9661 deletions
diff --git a/android/accounts/AccountManagerPerfTest.java b/android/accounts/AccountManagerPerfTest.java new file mode 100644 index 00000000..b9411fa4 --- /dev/null +++ b/android/accounts/AccountManagerPerfTest.java @@ -0,0 +1,55 @@ +/* + * 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 android.accounts; + +import static junit.framework.Assert.fail; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class AccountManagerPerfTest { + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Test + public void testGetAccounts() { + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final Context context = InstrumentationRegistry.getTargetContext(); + if (context.checkSelfPermission(Manifest.permission.GET_ACCOUNTS) + != PackageManager.PERMISSION_GRANTED) { + fail("Missing required GET_ACCOUNTS permission"); + } + AccountManager accountManager = AccountManager.get(context); + while (state.keepRunning()) { + accountManager.getAccounts(); + } + } +} diff --git a/android/app/Activity.java b/android/app/Activity.java index 85f73bb7..9d331a02 100644 --- a/android/app/Activity.java +++ b/android/app/Activity.java @@ -5867,10 +5867,11 @@ public class Activity extends ContextThemeWrapper } /** - * Returns complete component name of this activity. + * Returns the complete component name of this activity. * * @return Returns the complete component name for this activity */ + @Override public ComponentName getComponentName() { return mComponent; diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java index fc4c8d7f..8d9dc1fa 100644 --- a/android/app/ActivityManager.java +++ b/android/app/ActivityManager.java @@ -46,6 +46,7 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.BatteryStats; +import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -500,7 +501,7 @@ public class ActivityManager { public static final int PROCESS_STATE_SERVICE = 11; /** @hide Process is in the background running a receiver. Note that from the - * perspective of oom_adj receivers run at a higher foreground level, but for our + * perspective of oom_adj, receivers run at a higher foreground level, but for our * prioritization here that is not necessary and putting them below services means * many fewer changes in some process states as they receive broadcasts. */ public static final int PROCESS_STATE_RECEIVER = 12; @@ -524,6 +525,20 @@ public class ActivityManager { /** @hide Process does not exist. */ public static final int PROCESS_STATE_NONEXISTENT = 18; + // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added + // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must + // be updated to correctly map between them. + /** + * Maps ActivityManager.PROCESS_STATE_ values to ActivityManagerProto.ProcessState enum. + * + * @param amInt a process state of the form ActivityManager.PROCESS_STATE_ + * @return the value of the corresponding android.app.ActivityManagerProto's ProcessState enum. + * @hide + */ + public static final int processStateAmToProto(int amInt) { + return amInt * 100; + } + /** @hide The lowest process state number */ public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT; @@ -833,9 +848,8 @@ public class ActivityManager { /** * Returns true if this is a low-RAM device. Exactly whether a device is low-RAM * is ultimately up to the device configuration, but currently it generally means - * something in the class of a 512MB device with about a 800x480 or less screen. - * This is mostly intended to be used by apps to determine whether they should turn - * off certain features that require more RAM. + * something with 1GB or less of RAM. This is mostly intended to be used by apps + * to determine whether they should turn off certain features that require more RAM. */ public boolean isLowRamDevice() { return isLowRamDeviceStatic(); @@ -1596,6 +1610,9 @@ public class ActivityManager { public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags) throws SecurityException { try { + if (maxNum < 0) { + throw new IllegalArgumentException("The requested number of tasks should be >= 0"); + } return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1882,7 +1899,7 @@ public class ActivityManager { public List<RunningTaskInfo> getRunningTasks(int maxNum) throws SecurityException { try { - return getService().getTasks(maxNum, 0); + return getService().getTasks(maxNum); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3882,21 +3899,36 @@ public class ActivityManager { IBinder service = ServiceManager.checkService(name); if (service == null) { pw.println(" (Service not found)"); + pw.flush(); return; } - TransferPipe tp = null; - try { - pw.flush(); - tp = new TransferPipe(); - tp.setBufferPrefix(" "); - service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); - tp.go(fd, 10000); - } catch (Throwable e) { - if (tp != null) { - tp.kill(); + pw.flush(); + if (service instanceof Binder) { + // If this is a local object, it doesn't make sense to do an async dump with it, + // just directly dump. + try { + service.dump(fd, args); + } catch (Throwable e) { + pw.println("Failure dumping service:"); + e.printStackTrace(pw); + pw.flush(); + } + } else { + // Otherwise, it is remote, do the dump asynchronously to avoid blocking. + TransferPipe tp = null; + try { + pw.flush(); + tp = new TransferPipe(); + tp.setBufferPrefix(" "); + service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); + tp.go(fd, 10000); + } catch (Throwable e) { + if (tp != null) { + tp.kill(); + } + pw.println("Failure dumping service:"); + e.printStackTrace(pw); } - pw.println("Failure dumping service:"); - e.printStackTrace(pw); } } diff --git a/android/app/AlarmManager.java b/android/app/AlarmManager.java index 2813e8b9..55f9e289 100644 --- a/android/app/AlarmManager.java +++ b/android/app/AlarmManager.java @@ -33,6 +33,7 @@ import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import android.util.proto.ProtoOutputStream; import libcore.util.ZoneInfoDB; @@ -48,7 +49,7 @@ import java.lang.annotation.RetentionPolicy; * if it is not already running. Registered alarms are retained while the * device is asleep (and can optionally wake the device up if they go off * during that time), but will be cleared if it is turned off and rebooted. - * + * * <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's * onReceive() method is executing. This guarantees that the phone will not sleep * until you have finished handling the broadcast. Once onReceive() returns, the @@ -296,7 +297,7 @@ public class AlarmManager { * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates * how many past alarm events have been accumulated into this intent * broadcast. Recurring alarms that have gone undelivered because the - * phone was asleep may have a count greater than one when delivered. + * phone was asleep may have a count greater than one when delivered. * * <div class="note"> * <p> @@ -396,10 +397,10 @@ public class AlarmManager { * set a recurring alarm for the top of every hour but the phone was asleep * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens, * then the next alarm will be sent at 9:00. - * - * <p>If your application wants to allow the delivery times to drift in + * + * <p>If your application wants to allow the delivery times to drift in * order to guarantee that at least a certain time interval always elapses - * between alarms, then the approach to take is to use one-time alarms, + * between alarms, then the approach to take is to use one-time alarms, * scheduling the next one yourself when handling each alarm delivery. * * <p class="note"> @@ -1056,7 +1057,7 @@ public class AlarmManager { /** * Creates a new alarm clock description. * - * @param triggerTime time at which the underlying alarm is triggered in wall time + * @param triggerTime time at which the underlying alarm is triggered in wall time * milliseconds since the epoch * @param showIntent an intent that can be used to show or edit details of * the alarm clock. @@ -1089,7 +1090,7 @@ public class AlarmManager { * Returns an intent that can be used to show or edit details of the alarm clock in * the application that scheduled it. * - * <p class="note">Beware that any application can retrieve and send this intent, + * <p class="note">Beware that any application can retrieve and send this intent, * potentially with additional fields filled in. See * {@link PendingIntent#send(android.content.Context, int, android.content.Intent) * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()} @@ -1121,5 +1122,13 @@ public class AlarmManager { return new AlarmClockInfo[size]; } }; + + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime); + mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT); + proto.end(token); + } } } diff --git a/android/app/Notification.java b/android/app/Notification.java index fee7d6c8..8226e0fb 100644 --- a/android/app/Notification.java +++ b/android/app/Notification.java @@ -858,7 +858,7 @@ public class Notification implements Parcelable * * @hide */ - static public IBinder whitelistToken; + private IBinder mWhitelistToken; /** * Must be set by a process to start associating tokens with Notification objects @@ -1876,12 +1876,12 @@ public class Notification implements Parcelable { int version = parcel.readInt(); - whitelistToken = parcel.readStrongBinder(); - if (whitelistToken == null) { - whitelistToken = processWhitelistToken; + mWhitelistToken = parcel.readStrongBinder(); + if (mWhitelistToken == null) { + mWhitelistToken = processWhitelistToken; } // Propagate this token to all pending intents that are unmarshalled from the parcel. - parcel.setClassCookie(PendingIntent.class, whitelistToken); + parcel.setClassCookie(PendingIntent.class, mWhitelistToken); when = parcel.readLong(); creationTime = parcel.readLong(); @@ -1989,7 +1989,7 @@ public class Notification implements Parcelable * @hide */ public void cloneInto(Notification that, boolean heavy) { - that.whitelistToken = this.whitelistToken; + that.mWhitelistToken = this.mWhitelistToken; that.when = this.when; that.creationTime = this.creationTime; that.mSmallIcon = this.mSmallIcon; @@ -2219,7 +2219,7 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); - parcel.writeStrongBinder(whitelistToken); + parcel.writeStrongBinder(mWhitelistToken); parcel.writeLong(when); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { @@ -4981,6 +4981,8 @@ public class Notification implements Parcelable mN.flags |= FLAG_SHOW_LIGHTS; } + mN.allPendingIntents = null; + return mN; } diff --git a/android/app/PendingIntent.java b/android/app/PendingIntent.java index a25c2267..8b76cc7c 100644 --- a/android/app/PendingIntent.java +++ b/android/app/PendingIntent.java @@ -33,6 +33,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.util.AndroidException; +import android.util.proto.ProtoOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1081,7 +1082,16 @@ public final class PendingIntent implements Parcelable { sb.append('}'); return sb.toString(); } - + + /** @hide */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + if (mTarget != null) { + proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString()); + } + proto.end(token); + } + public int describeContents() { return 0; } @@ -1119,8 +1129,13 @@ public final class PendingIntent implements Parcelable { */ public static void writePendingIntentOrNullToParcel(@Nullable PendingIntent sender, @NonNull Parcel out) { - out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() - : null); + out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null); + if (sender != null) { + OnMarshaledListener listener = sOnMarshaledListener.get(); + if (listener != null) { + listener.onMarshaled(sender, out, 0 /* flags */); + } + } } /** diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java index 251863ca..de27b4fd 100644 --- a/android/app/WindowConfiguration.java +++ b/android/app/WindowConfiguration.java @@ -102,7 +102,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu public static final int ACTIVITY_TYPE_STANDARD = 1; /** Home/Launcher activity type. */ public static final int ACTIVITY_TYPE_HOME = 2; - /** Recents/Overview activity type. */ + /** Recents/Overview activity type. There is only one activity with this type in the system. */ public static final int ACTIVITY_TYPE_RECENTS = 3; /** Assistant activity type. */ public static final int ACTIVITY_TYPE_ASSISTANT = 4; diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java index ab8edee7..772c6d60 100644 --- a/android/app/admin/DevicePolicyManager.java +++ b/android/app/admin/DevicePolicyManager.java @@ -1542,6 +1542,92 @@ public class DevicePolicyManager { public @interface ProvisioningPreCondition {} /** + * Disable all configurable SystemUI features during LockTask mode. This includes, + * <ul> + * <li>system info area in the status bar (connectivity icons, clock, etc.) + * <li>notifications (including alerts, icons, and the notification shade) + * <li>Home button + * <li>Recents button and UI + * <li>global actions menu (i.e. power button menu) + * <li>keyguard + * </ul> + * + * This is the default configuration for LockTask. + * + * @see #setLockTaskFeatures(ComponentName, int) + */ + public static final int LOCK_TASK_FEATURE_NONE = 0; + + /** + * Enable the system info area in the status bar during LockTask mode. The system info area + * usually occupies the right side of the status bar (although this can differ across OEMs). It + * includes all system information indicators, such as date and time, connectivity, battery, + * vibration mode, etc. + * + * @see #setLockTaskFeatures(ComponentName, int) + */ + public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; + + /** + * Enable notifications during LockTask mode. This includes notification icons on the status + * bar, heads-up notifications, and the expandable notification shade. Note that the Quick + * Settings panel will still be disabled. + * + * @see #setLockTaskFeatures(ComponentName, int) + */ + public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 1 << 1; + + /** + * Enable the Home button during LockTask mode. Note that if a custom launcher is used, it has + * to be registered as the default launcher with + * {@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}, and its + * package needs to be whitelisted for LockTask with + * {@link #setLockTaskPackages(ComponentName, String[])}. + * + * @see #setLockTaskFeatures(ComponentName, int) + */ + public static final int LOCK_TASK_FEATURE_HOME = 1 << 2; + + /** + * Enable the Recents button and the Recents screen during LockTask mode. + * + * @see #setLockTaskFeatures(ComponentName, int) + */ + public static final int LOCK_TASK_FEATURE_RECENTS = 1 << 3; + + /** + * Enable the global actions dialog during LockTask mode. This is the dialog that shows up when + * the user long-presses the power button, for example. Note that the user may not be able to + * power off the device if this flag is not set. + * + * @see #setLockTaskFeatures(ComponentName, int) + */ + public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 1 << 4; + + /** + * Enable the keyguard during LockTask mode. Note that if the keyguard is already disabled with + * {@link #setKeyguardDisabled(ComponentName, boolean)}, setting this flag will have no effect. + * If this flag is not set, the keyguard will not be shown even if the user has a lock screen + * credential. + * + * @see #setLockTaskFeatures(ComponentName, int) + */ + public static final int LOCK_TASK_FEATURE_KEYGUARD = 1 << 5; + + /** + * Flags supplied to {@link #setLockTaskFeatures(ComponentName, int)}. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = {LOCK_TASK_FEATURE_NONE, LOCK_TASK_FEATURE_SYSTEM_INFO, + LOCK_TASK_FEATURE_NOTIFICATIONS, LOCK_TASK_FEATURE_HOME, + LOCK_TASK_FEATURE_RECENTS, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, + LOCK_TASK_FEATURE_KEYGUARD}) + public @interface LockTaskFeature {} + + /** * Service action: Action for a service that device owner and profile owner can optionally * own. If a device owner or a profile owner has such a service, the system tries to keep * a bound connection to it, in order to keep their process always running. @@ -6484,6 +6570,61 @@ public class DevicePolicyManager { } /** + * Sets which system features to enable for LockTask mode. + * <p> + * Feature flags set through this method will only take effect for the duration when the device + * is in LockTask mode. If this method is not called, none of the features listed here will be + * enabled. + * <p> + * This function can only be called by the device owner or by a profile owner of a user/profile + * that is affiliated with the device owner user. See {@link #setAffiliationIds}. Any features + * set via this method will be cleared if the user becomes unaffiliated. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param flags Bitfield of feature flags: + * {@link #LOCK_TASK_FEATURE_NONE} (default), + * {@link #LOCK_TASK_FEATURE_SYSTEM_INFO}, + * {@link #LOCK_TASK_FEATURE_NOTIFICATIONS}, + * {@link #LOCK_TASK_FEATURE_HOME}, + * {@link #LOCK_TASK_FEATURE_RECENTS}, + * {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}, + * {@link #LOCK_TASK_FEATURE_KEYGUARD} + * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of + * an affiliated user or profile. + */ + public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) { + throwIfParentInstance("setLockTaskFeatures"); + if (mService != null) { + try { + mService.setLockTaskFeatures(admin, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Gets which system features are enabled for LockTask mode. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list. + * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of + * an affiliated user or profile. + * @see #setLockTaskFeatures(ComponentName, int) + */ + public @LockTaskFeature int getLockTaskFeatures(@NonNull ComponentName admin) { + throwIfParentInstance("getLockTaskFeatures"); + if (mService != null) { + try { + return mService.getLockTaskFeatures(admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return 0; + } + + /** * Called by device owners to update {@link android.provider.Settings.Global} settings. * Validation that the value of the setting is in the correct form for the setting type should * be performed by the caller. @@ -6901,6 +7042,12 @@ public class DevicePolicyManager { * Called by device owner to disable the status bar. Disabling the status bar blocks * notifications, quick settings and other screen overlays that allow escaping from a single use * device. + * <p> + * <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the + * status bar in LockTask mode can be configured with + * {@link #setLockTaskFeatures(ComponentName, int)}. Calls to this method when the device is in + * LockTask mode will be registered, but will only take effect when the device leaves LockTask + * mode. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled {@code true} disables the status bar, {@code false} reenables it. diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java index 1434c9ba..b640bd5b 100644 --- a/android/app/job/JobInfo.java +++ b/android/app/job/JobInfo.java @@ -18,6 +18,7 @@ package android.app.job; import static android.util.TimeUtils.formatDuration; +import android.annotation.BytesLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -71,6 +72,9 @@ public class JobInfo implements Parcelable { /** This job requires metered connectivity such as most cellular data networks. */ public static final int NETWORK_TYPE_METERED = 4; + /** Sentinel value indicating that bytes are unknown. */ + public static final int NETWORK_BYTES_UNKNOWN = -1; + /** * Amount of backoff a job has initially by default, in milliseconds. */ @@ -250,6 +254,7 @@ public class JobInfo implements Parcelable { private final boolean hasEarlyConstraint; private final boolean hasLateConstraint; private final int networkType; + private final long networkBytes; private final long minLatencyMillis; private final long maxExecutionDelayMillis; private final boolean isPeriodic; @@ -387,6 +392,18 @@ public class JobInfo implements Parcelable { } /** + * Return the estimated size of network traffic that will be performed by + * this job, in bytes. + * + * @return Estimated size of network traffic, or + * {@link #NETWORK_BYTES_UNKNOWN} when unknown. + * @see Builder#setEstimatedNetworkBytes(long) + */ + public @BytesLong long getEstimatedNetworkBytes() { + return networkBytes; + } + + /** * Set for a job that does not recur periodically, to specify a delay after which the job * will be eligible for execution. This value is not set if the job recurs periodically. */ @@ -524,6 +541,9 @@ public class JobInfo implements Parcelable { if (networkType != j.networkType) { return false; } + if (networkBytes != j.networkBytes) { + return false; + } if (minLatencyMillis != j.minLatencyMillis) { return false; } @@ -582,6 +602,7 @@ public class JobInfo implements Parcelable { hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint); hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint); hashCode = 31 * hashCode + networkType; + hashCode = 31 * hashCode + Long.hashCode(networkBytes); hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis); hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis); hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic); @@ -612,6 +633,7 @@ public class JobInfo implements Parcelable { triggerContentUpdateDelay = in.readLong(); triggerContentMaxDelay = in.readLong(); networkType = in.readInt(); + networkBytes = in.readLong(); minLatencyMillis = in.readLong(); maxExecutionDelayMillis = in.readLong(); isPeriodic = in.readInt() == 1; @@ -640,6 +662,7 @@ public class JobInfo implements Parcelable { triggerContentUpdateDelay = b.mTriggerContentUpdateDelay; triggerContentMaxDelay = b.mTriggerContentMaxDelay; networkType = b.mNetworkType; + networkBytes = b.mNetworkBytes; minLatencyMillis = b.mMinLatencyMillis; maxExecutionDelayMillis = b.mMaxExecutionDelayMillis; isPeriodic = b.mIsPeriodic; @@ -677,6 +700,7 @@ public class JobInfo implements Parcelable { out.writeLong(triggerContentUpdateDelay); out.writeLong(triggerContentMaxDelay); out.writeInt(networkType); + out.writeLong(networkBytes); out.writeLong(minLatencyMillis); out.writeLong(maxExecutionDelayMillis); out.writeInt(isPeriodic ? 1 : 0); @@ -810,6 +834,7 @@ public class JobInfo implements Parcelable { // Requirements. private int mConstraintFlags; private int mNetworkType; + private long mNetworkBytes = NETWORK_BYTES_UNKNOWN; private ArrayList<TriggerContentUri> mTriggerContentUris; private long mTriggerContentUpdateDelay = -1; private long mTriggerContentMaxDelay = -1; @@ -909,12 +934,21 @@ public class JobInfo implements Parcelable { } /** - * Set some description of the kind of network type your job needs to have. - * Not calling this function means the network is not necessary, as the default is - * {@link #NETWORK_TYPE_NONE}. - * Bear in mind that calling this function defines network as a strict requirement for your - * job. If the network requested is not available your job will never run. See - * {@link #setOverrideDeadline(long)} to change this behaviour. + * Set some description of the kind of network type your job needs to + * have. Not calling this function means the network is not necessary, + * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that + * calling this function defines network as a strict requirement for + * your job. If the network requested is not available your job will + * never run. See {@link #setOverrideDeadline(long)} to change this + * behaviour. + * <p class="note"> + * Note: When your job executes in + * {@link JobService#onStartJob(JobParameters)}, be sure to use the + * specific network returned by {@link JobParameters#getNetwork()}, + * otherwise you'll use the default network which may not meet this + * constraint. + * + * @see JobParameters#getNetwork() */ public Builder setRequiredNetworkType(@NetworkType int networkType) { mNetworkType = networkType; @@ -922,6 +956,43 @@ public class JobInfo implements Parcelable { } /** + * Set the estimated size of network traffic that will be performed by + * this job, in bytes. + * <p> + * Apps are encouraged to provide values that are as accurate as + * possible, but when the exact size isn't available, an + * order-of-magnitude estimate can be provided instead. Here are some + * specific examples: + * <ul> + * <li>A job that is backing up a photo knows the exact size of that + * photo, so it should provide that size as the estimate. + * <li>A job that refreshes top news stories wouldn't know an exact + * size, but if the size is expected to be consistently around 100KB, it + * can provide that order-of-magnitude value as the estimate. + * <li>A job that synchronizes email could end up using an extreme range + * of data, from under 1KB when nothing has changed, to dozens of MB + * when there are new emails with attachments. Jobs that cannot provide + * reasonable estimates should leave this estimated value undefined. + * </ul> + * Note that the system may choose to delay jobs with large network + * usage estimates when the device has a poor network connection, in + * order to save battery. + * + * @param networkBytes The estimated size of network traffic that will + * be performed by this job, in bytes. This value only + * reflects the traffic that will be performed by the base + * job; if you're using {@link JobWorkItem} then you also + * need to define the network traffic used by each work item + * when constructing them. + * @see JobInfo#getEstimatedNetworkBytes() + * @see JobWorkItem#JobWorkItem(android.content.Intent, long) + */ + public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) { + mNetworkBytes = networkBytes; + return this; + } + + /** * Specify that to run this job, the device must be charging (or be a * non-battery-powered device connected to permanent power, such as Android TV * devices). This defaults to {@code false}. @@ -1147,6 +1218,11 @@ public class JobInfo implements Parcelable { throw new IllegalArgumentException("You're trying to build a job with no " + "constraints, this is not allowed."); } + // Check that network estimates require network type + if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) { + throw new IllegalArgumentException( + "Can't provide estimated network usage without requiring a network"); + } // Check that a deadline was not set on a periodic job. if (mIsPeriodic) { if (mMaxExecutionDelayMillis != 0L) { diff --git a/android/app/job/JobParameters.java b/android/app/job/JobParameters.java index a6f6be22..5053dc6f 100644 --- a/android/app/job/JobParameters.java +++ b/android/app/job/JobParameters.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.IJobCallback; import android.content.ClipData; +import android.net.Network; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -66,6 +67,7 @@ public class JobParameters implements Parcelable { private final boolean overrideDeadlineExpired; private final Uri[] mTriggeredContentUris; private final String[] mTriggeredContentAuthorities; + private final Network network; private int stopReason; // Default value of stopReason is REASON_CANCELED @@ -73,7 +75,7 @@ public class JobParameters implements Parcelable { public JobParameters(IBinder callback, int jobId, PersistableBundle extras, Bundle transientExtras, ClipData clipData, int clipGrantFlags, boolean overrideDeadlineExpired, Uri[] triggeredContentUris, - String[] triggeredContentAuthorities) { + String[] triggeredContentAuthorities, Network network) { this.jobId = jobId; this.extras = extras; this.transientExtras = transientExtras; @@ -83,6 +85,7 @@ public class JobParameters implements Parcelable { this.overrideDeadlineExpired = overrideDeadlineExpired; this.mTriggeredContentUris = triggeredContentUris; this.mTriggeredContentAuthorities = triggeredContentAuthorities; + this.network = network; } /** @@ -171,6 +174,28 @@ public class JobParameters implements Parcelable { } /** + * Return the network that should be used to perform any network requests + * for this job. + * <p> + * Devices may have multiple active network connections simultaneously, or + * they may not have a default network route at all. To correctly handle all + * situations like this, your job should always use the network returned by + * this method instead of implicitly using the default network route. + * <p> + * Note that the system may relax the constraints you originally requested, + * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over + * a metered network when there is a surplus of metered data available. + * + * @return the network that should be used to perform any network requests + * for this job, or {@code null} if this job didn't set any required + * network type. + * @see JobInfo.Builder#setRequiredNetworkType(int) + */ + public @Nullable Network getNetwork() { + return network; + } + + /** * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their * currently running job. Calling this method when there is no more work available and all * previously dequeued work has been completed will result in the system taking care of @@ -257,6 +282,11 @@ public class JobParameters implements Parcelable { overrideDeadlineExpired = in.readInt() == 1; mTriggeredContentUris = in.createTypedArray(Uri.CREATOR); mTriggeredContentAuthorities = in.createStringArray(); + if (in.readInt() != 0) { + network = Network.CREATOR.createFromParcel(in); + } else { + network = null; + } stopReason = in.readInt(); } @@ -286,6 +316,12 @@ public class JobParameters implements Parcelable { dest.writeInt(overrideDeadlineExpired ? 1 : 0); dest.writeTypedArray(mTriggeredContentUris, flags); dest.writeStringArray(mTriggeredContentAuthorities); + if (network != null) { + dest.writeInt(1); + network.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } dest.writeInt(stopReason); } diff --git a/android/app/job/JobWorkItem.java b/android/app/job/JobWorkItem.java index 0eb0450e..1c46e8ec 100644 --- a/android/app/job/JobWorkItem.java +++ b/android/app/job/JobWorkItem.java @@ -16,6 +16,7 @@ package android.app.job; +import android.annotation.BytesLong; import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; @@ -27,6 +28,7 @@ import android.os.Parcelable; */ final public class JobWorkItem implements Parcelable { final Intent mIntent; + final long mNetworkBytes; int mDeliveryCount; int mWorkId; Object mGrants; @@ -39,6 +41,22 @@ final public class JobWorkItem implements Parcelable { */ public JobWorkItem(Intent intent) { mIntent = intent; + mNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN; + } + + /** + * Create a new piece of work, which can be submitted to + * {@link JobScheduler#enqueue JobScheduler.enqueue}. + * + * @param intent The general Intent describing this work. + * @param networkBytes The estimated size of network traffic that will be + * performed by this job work item, in bytes. See + * {@link JobInfo.Builder#setEstimatedNetworkBytes(long)} for + * details about how to estimate. + */ + public JobWorkItem(Intent intent, @BytesLong long networkBytes) { + mIntent = intent; + mNetworkBytes = networkBytes; } /** @@ -49,6 +67,17 @@ final public class JobWorkItem implements Parcelable { } /** + * Return the estimated size of network traffic that will be performed by + * this job work item, in bytes. + * + * @return estimated size, or {@link JobInfo#NETWORK_BYTES_UNKNOWN} when + * unknown. + */ + public @BytesLong long getEstimatedNetworkBytes() { + return mNetworkBytes; + } + + /** * Return the count of the number of times this work item has been delivered * to the job. The value will be > 1 if it has been redelivered because the job * was stopped or crashed while it had previously been delivered but before the @@ -99,6 +128,10 @@ final public class JobWorkItem implements Parcelable { sb.append(mWorkId); sb.append(" intent="); sb.append(mIntent); + if (mNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { + sb.append(" networkBytes="); + sb.append(mNetworkBytes); + } if (mDeliveryCount != 0) { sb.append(" dcount="); sb.append(mDeliveryCount); @@ -118,6 +151,7 @@ final public class JobWorkItem implements Parcelable { } else { out.writeInt(0); } + out.writeLong(mNetworkBytes); out.writeInt(mDeliveryCount); out.writeInt(mWorkId); } @@ -139,6 +173,7 @@ final public class JobWorkItem implements Parcelable { } else { mIntent = null; } + mNetworkBytes = in.readLong(); mDeliveryCount = in.readInt(); mWorkId = in.readInt(); } diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java index 7f9f74b4..f6b6b869 100644 --- a/android/app/slice/Slice.java +++ b/android/app/slice/Slice.java @@ -154,25 +154,6 @@ public final class Slice implements Parcelable { return Arrays.asList(mHints); } - /** - * @hide - */ - public SliceItem getPrimaryIcon() { - for (SliceItem item : getItems()) { - if (item.getType() == SliceItem.TYPE_IMAGE) { - return item; - } - if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST)) - && !item.hasHint(Slice.HINT_ACTIONS) - && !item.hasHint(Slice.HINT_LIST_ITEM) - && (item.getType() != SliceItem.TYPE_ACTION)) { - SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE); - if (icon != null) return icon; - } - } - return null; - } - @Override public void writeToParcel(Parcel dest, int flags) { dest.writeStringArray(mHints); @@ -405,6 +386,9 @@ public final class Slice implements Parcelable { final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE, null, extras); Bundle.setDefusable(res, true); + if (res == null) { + return null; + } return res.getParcelable(SliceProvider.EXTRA_SLICE); } catch (RemoteException e) { // Arbitrary and not worth documenting, as Activity diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java index df87b455..33825b4b 100644 --- a/android/app/slice/SliceProvider.java +++ b/android/app/slice/SliceProvider.java @@ -156,27 +156,34 @@ public abstract class SliceProvider extends ContentProvider { } private Slice handleBindSlice(Uri sliceUri) { - Slice[] output = new Slice[1]; - CountDownLatch latch = new CountDownLatch(1); - Handler mainHandler = new Handler(Looper.getMainLooper()); - mainHandler.post(() -> { - ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); - try { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectAll() - .penaltyDeath() - .build()); - output[0] = onBindSlice(sliceUri); - } finally { - StrictMode.setThreadPolicy(oldPolicy); + if (Looper.myLooper() == Looper.getMainLooper()) { + return onBindSliceStrict(sliceUri); + } else { + CountDownLatch latch = new CountDownLatch(1); + Slice[] output = new Slice[1]; + Handler.getMain().post(() -> { + output[0] = onBindSliceStrict(sliceUri); latch.countDown(); + }); + try { + latch.await(); + return output[0]; + } catch (InterruptedException e) { + throw new RuntimeException(e); } - }); + } + } + + private Slice onBindSliceStrict(Uri sliceUri) { + ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); try { - latch.await(); - return output[0]; - } catch (InterruptedException e) { - throw new RuntimeException(e); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyDeath() + .build()); + return onBindSlice(sliceUri); + } finally { + StrictMode.setThreadPolicy(oldPolicy); } } } diff --git a/android/app/slice/SliceQuery.java b/android/app/slice/SliceQuery.java index d1fe2c90..9943c492 100644 --- a/android/app/slice/SliceQuery.java +++ b/android/app/slice/SliceQuery.java @@ -35,6 +35,27 @@ public class SliceQuery { /** * @hide */ + public static SliceItem getPrimaryIcon(Slice slice) { + for (SliceItem item : slice.getItems()) { + if (item.getType() == SliceItem.TYPE_IMAGE) { + return item; + } + if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST)) + && !item.hasHint(Slice.HINT_ACTIONS) + && !item.hasHint(Slice.HINT_LIST_ITEM) + && (item.getType() != SliceItem.TYPE_ACTION)) { + SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE); + if (icon != null) { + return icon; + } + } + } + return null; + } + + /** + * @hide + */ public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) { SliceItem ret = null; while (ret == null && list.size() != 0) { diff --git a/android/app/slice/views/SliceView.java b/android/app/slice/views/SliceView.java deleted file mode 100644 index 32484fca..00000000 --- a/android/app/slice/views/SliceView.java +++ /dev/null @@ -1,251 +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 android.app.slice.views; - -import android.annotation.StringDef; -import android.app.slice.Slice; -import android.app.slice.SliceItem; -import android.app.slice.SliceQuery; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.ColorDrawable; -import android.net.Uri; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; - -import java.util.List; - -/** - * A view that can display a {@link Slice} in different {@link SliceMode}'s. - * - * @hide - */ -public class SliceView extends LinearLayout { - - private static final String TAG = "SliceView"; - - /** - * @hide - */ - public abstract static class SliceModeView extends FrameLayout { - - public SliceModeView(Context context) { - super(context); - } - - /** - * @return the {@link SliceMode} of the slice being presented. - */ - public abstract String getMode(); - - /** - * @param slice the slice to show in this view. - */ - public abstract void setSlice(Slice slice); - } - - /** - * @hide - */ - @StringDef({ - MODE_SMALL, MODE_LARGE, MODE_SHORTCUT - }) - public @interface SliceMode {} - - /** - * Mode indicating this slice should be presented in small template format. - */ - public static final String MODE_SMALL = "SLICE_SMALL"; - /** - * Mode indicating this slice should be presented in large template format. - */ - public static final String MODE_LARGE = "SLICE_LARGE"; - /** - * Mode indicating this slice should be presented as an icon. - */ - public static final String MODE_SHORTCUT = "SLICE_ICON"; - - /** - * Will select the type of slice binding based on size of the View. TODO: Put in some info about - * that selection. - */ - private static final String MODE_AUTO = "auto"; - - private String mMode = MODE_AUTO; - private SliceModeView mCurrentView; - private final ActionRow mActions; - private Slice mCurrentSlice; - private boolean mShowActions = true; - - /** - * Simple constructor to create a slice view from code. - * - * @param context The context the view is running in. - */ - public SliceView(Context context) { - super(context); - setOrientation(LinearLayout.VERTICAL); - mActions = new ActionRow(mContext, true); - mActions.setBackground(new ColorDrawable(0xffeeeeee)); - mCurrentView = new LargeTemplateView(mContext); - addView(mCurrentView); - addView(mActions); - } - - /** - * @hide - */ - public void bindSlice(Intent intent) { - // TODO - } - - /** - * Binds this view to the {@link Slice} associated with the provided {@link Uri}. - */ - public void bindSlice(Uri sliceUri) { - validate(sliceUri); - Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri); - bindSlice(s); - } - - /** - * Binds this view to the provided {@link Slice}. - */ - public void bindSlice(Slice slice) { - mCurrentSlice = slice; - if (mCurrentSlice != null) { - reinflate(); - } - } - - /** - * Call to clean up the view. - */ - public void unbindSlice() { - mCurrentSlice = null; - } - - /** - * Set the {@link SliceMode} this view should present in. - */ - public void setMode(@SliceMode String mode) { - setMode(mode, false /* animate */); - } - - /** - * @hide - */ - public void setMode(@SliceMode String mode, boolean animate) { - if (animate) { - Log.e(TAG, "Animation not supported yet"); - } - mMode = mode; - reinflate(); - } - - /** - * @return the {@link SliceMode} this view is presenting in. - */ - public @SliceMode String getMode() { - if (mMode.equals(MODE_AUTO)) { - return MODE_LARGE; - } - return mMode; - } - - /** - * @hide - * - * Whether this view should show a row of actions with it. - */ - public void setShowActionRow(boolean show) { - mShowActions = show; - reinflate(); - } - - private SliceModeView createView(String mode) { - switch (mode) { - case MODE_SHORTCUT: - return new ShortcutView(getContext()); - case MODE_SMALL: - return new SmallTemplateView(getContext()); - } - return new LargeTemplateView(getContext()); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - unbindSlice(); - } - - private void reinflate() { - if (mCurrentSlice == null) { - return; - } - // TODO: Smarter mapping here from one state to the next. - SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR); - List<SliceItem> items = mCurrentSlice.getItems(); - SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE, - Slice.HINT_ACTIONS, - Slice.HINT_ALT); - String mode = getMode(); - if (!mode.equals(mCurrentView.getMode())) { - removeAllViews(); - mCurrentView = createView(mode); - addView(mCurrentView); - addView(mActions); - } - if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) { - mCurrentView.setVisibility(View.VISIBLE); - mCurrentView.setSlice(mCurrentSlice); - } else { - mCurrentView.setVisibility(View.GONE); - } - - boolean showActions = mShowActions && actionRow != null - && !mode.equals(MODE_SHORTCUT); - if (showActions) { - mActions.setActions(actionRow, color); - mActions.setVisibility(View.VISIBLE); - } else { - mActions.setVisibility(View.GONE); - } - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - // TODO -- may need to rethink for AGSA - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { - requestDisallowInterceptTouchEvent(true); - } - return super.onInterceptTouchEvent(ev); - } - - private static void validate(Uri sliceUri) { - if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) { - throw new RuntimeException("Invalid uri " + sliceUri); - } - if (sliceUri.getPathSegments().size() == 0) { - throw new RuntimeException("Invalid uri " + sliceUri); - } - } -} diff --git a/android/app/slice/views/ActionRow.java b/android/app/slice/widget/ActionRow.java index c7d99f7f..c96e6304 100644 --- a/android/app/slice/views/ActionRow.java +++ b/android/app/slice/widget/ActionRow.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; diff --git a/android/app/slice/views/GridView.java b/android/app/slice/widget/GridView.java index 6f30c507..67a3c671 100644 --- a/android/app/slice/views/GridView.java +++ b/android/app/slice/widget/GridView.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.app.slice.Slice; import android.app.slice.SliceItem; -import android.app.slice.views.LargeSliceAdapter.SliceListView; +import android.app.slice.widget.LargeSliceAdapter.SliceListView; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; diff --git a/android/app/slice/views/LargeSliceAdapter.java b/android/app/slice/widget/LargeSliceAdapter.java index 6794ff98..267fff6a 100644 --- a/android/app/slice/views/LargeSliceAdapter.java +++ b/android/app/slice/widget/LargeSliceAdapter.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import android.app.slice.Slice; import android.app.slice.SliceItem; import android.app.slice.SliceQuery; -import android.app.slice.views.LargeSliceAdapter.SliceViewHolder; +import android.app.slice.widget.LargeSliceAdapter.SliceViewHolder; import android.content.Context; import android.util.ArrayMap; import android.view.LayoutInflater; @@ -71,7 +71,7 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> { @Override public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View v = inflateforType(viewType); + View v = inflateForType(viewType); v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); return new SliceViewHolder(v); } @@ -104,7 +104,7 @@ public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> { } } - private View inflateforType(int viewType) { + private View inflateForType(int viewType) { switch (viewType) { case TYPE_REMOTE_VIEWS: return new FrameLayout(mContext); diff --git a/android/app/slice/views/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java index 9e225162..f45b2a8f 100644 --- a/android/app/slice/views/LargeTemplateView.java +++ b/android/app/slice/widget/LargeTemplateView.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.app.slice.Slice; import android.app.slice.SliceItem; import android.app.slice.SliceQuery; -import android.app.slice.views.SliceView.SliceModeView; +import android.app.slice.widget.SliceView.SliceModeView; import android.content.Context; import android.util.TypedValue; @@ -35,11 +35,13 @@ import java.util.List; * @hide */ public class LargeTemplateView extends SliceModeView { + private final LargeSliceAdapter mAdapter; private final RecyclerView mRecyclerView; private final int mDefaultHeight; private final int mMaxHeight; private Slice mSlice; + private boolean mIsScrollable; public LargeTemplateView(Context context) { super(context); @@ -49,9 +51,6 @@ public class LargeTemplateView extends SliceModeView { mAdapter = new LargeSliceAdapter(context); mRecyclerView.setAdapter(mAdapter); addView(mRecyclerView); - int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300, - getResources().getDisplayMetrics()); - setLayoutParams(new LayoutParams(width, WRAP_CONTENT)); mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics()); mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, @@ -68,7 +67,7 @@ public class LargeTemplateView extends SliceModeView { mRecyclerView.getLayoutParams().height = WRAP_CONTENT; super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mRecyclerView.getMeasuredHeight() > mMaxHeight - || mSlice.hasHint(Slice.HINT_PARTIAL)) { + || (mSlice != null && mSlice.hasHint(Slice.HINT_PARTIAL))) { mRecyclerView.getLayoutParams().height = mDefaultHeight; } else { mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight(); @@ -112,4 +111,12 @@ public class LargeTemplateView extends SliceModeView { sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM)); items.addAll(sliceItems); } + + /** + * Whether or not the content in this template should be scrollable. + */ + public void setScrollable(boolean isScrollable) { + // TODO -- restrict / enable how much this view can show + mIsScrollable = isScrollable; + } } diff --git a/android/app/slice/views/MessageView.java b/android/app/slice/widget/MessageView.java index 77252bf2..3124398e 100644 --- a/android/app/slice/views/MessageView.java +++ b/android/app/slice/widget/MessageView.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import android.app.slice.Slice; import android.app.slice.SliceItem; import android.app.slice.SliceQuery; -import android.app.slice.views.LargeSliceAdapter.SliceListView; +import android.app.slice.widget.LargeSliceAdapter.SliceListView; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; diff --git a/android/app/slice/views/RemoteInputView.java b/android/app/slice/widget/RemoteInputView.java index e53cb1ea..6eff5afb 100644 --- a/android/app/slice/views/RemoteInputView.java +++ b/android/app/slice/widget/RemoteInputView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import android.animation.Animator; import android.app.Notification; diff --git a/android/app/slice/views/ShortcutView.java b/android/app/slice/widget/ShortcutView.java index b6790c7d..0bca8ce2 100644 --- a/android/app/slice/views/ShortcutView.java +++ b/android/app/slice/widget/ShortcutView.java @@ -14,21 +14,20 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; import android.app.slice.Slice; import android.app.slice.SliceItem; import android.app.slice.SliceQuery; -import android.app.slice.views.SliceView.SliceModeView; +import android.app.slice.widget.SliceView.SliceModeView; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.net.Uri; -import android.view.ViewGroup; import com.android.internal.R; @@ -46,17 +45,14 @@ public class ShortcutView extends SliceModeView { public ShortcutView(Context context) { super(context); - mLargeIconSize = getContext().getResources() - .getDimensionPixelSize(R.dimen.slice_shortcut_size); mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size); - setLayoutParams(new ViewGroup.LayoutParams(mLargeIconSize, mLargeIconSize)); } @Override public void setSlice(Slice slice) { removeAllViews(); SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION); - SliceItem iconItem = slice.getPrimaryIcon(); + SliceItem iconItem = SliceQuery.getPrimaryIcon(slice); SliceItem textItem = sliceItem != null ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT) : SliceQuery.find(slice, SliceItem.TYPE_TEXT); diff --git a/android/app/slice/widget/SliceView.java b/android/app/slice/widget/SliceView.java new file mode 100644 index 00000000..5bafbc03 --- /dev/null +++ b/android/app/slice/widget/SliceView.java @@ -0,0 +1,402 @@ +/* + * 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 android.app.slice.widget; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.app.slice.Slice; +import android.app.slice.SliceItem; +import android.app.slice.SliceQuery; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.android.internal.R; +import com.android.internal.util.Preconditions; + +import java.util.List; + +/** + * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is + * able to present slice content in a templated format outside of the associated app. The way this + * content is displayed depends on the structure of the slice, the hints associated with the + * content, and the mode that SliceView is configured for. The modes that SliceView supports are: + * <ul> + * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main + * content or action associated with the slice.</li> + * <li><b>Small</b>: The small format has a restricted height and can present a single + * {@link SliceItem} or a limited collection of items.</li> + * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is + * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can + * comfortably fit.</li> + * </ul> + * <p> + * When constructing a slice, the contents of it can be annotated with hints, these provide the OS + * with some information on how the content should be displayed. For example, text annotated with + * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated + * with {@link Slice#HINT_LIST} would present the child items of that slice in a list. + * <p> + * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content + * observer will be set for that uri and the view will update if there are any changes to the slice. + * To use this the app must have a special permission to bind to the slice (see + * {@link android.Manifest.permission#BIND_SLICE}). + * <p> + * Example usage: + * + * <pre class="prettyprint"> + * SliceView v = new SliceView(getContext()); + * v.setMode(desiredMode); + * v.setSlice(sliceUri); + * </pre> + */ +public class SliceView extends ViewGroup { + + private static final String TAG = "SliceView"; + + /** + * @hide + */ + public abstract static class SliceModeView extends FrameLayout { + + public SliceModeView(Context context) { + super(context); + } + + /** + * @return the mode of the slice being presented. + */ + public abstract String getMode(); + + /** + * @param slice the slice to show in this view. + */ + public abstract void setSlice(Slice slice); + } + + /** + * @hide + */ + @StringDef({ + MODE_SMALL, MODE_LARGE, MODE_SHORTCUT + }) + public @interface SliceMode {} + + /** + * Mode indicating this slice should be presented in small template format. + */ + public static final String MODE_SMALL = "SLICE_SMALL"; + /** + * Mode indicating this slice should be presented in large template format. + */ + public static final String MODE_LARGE = "SLICE_LARGE"; + /** + * Mode indicating this slice should be presented as an icon. + */ + public static final String MODE_SHORTCUT = "SLICE_ICON"; + + /** + * Will select the type of slice binding based on size of the View. TODO: Put in some info about + * that selection. + */ + private static final String MODE_AUTO = "auto"; + + private String mMode = MODE_AUTO; + private SliceModeView mCurrentView; + private final ActionRow mActions; + private Slice mCurrentSlice; + private boolean mShowActions = true; + private boolean mIsScrollable; + private SliceObserver mObserver; + private final int mShortcutSize; + + public SliceView(Context context) { + this(context, null); + } + + public SliceView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mObserver = new SliceObserver(new Handler(Looper.getMainLooper())); + mActions = new ActionRow(mContext, true); + mActions.setBackground(new ColorDrawable(0xffeeeeee)); + mCurrentView = new LargeTemplateView(mContext); + addView(mCurrentView, getChildLp(mCurrentView)); + addView(mActions, getChildLp(mActions)); + mShortcutSize = getContext().getResources() + .getDimensionPixelSize(R.dimen.slice_shortcut_size); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureChildren(widthMeasureSpec, heightMeasureSpec); + int actionHeight = mActions.getVisibility() != View.GONE + ? mActions.getMeasuredHeight() + : 0; + int newHeightSpec = MeasureSpec.makeMeasureSpec( + mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY); + int width = MeasureSpec.getSize(widthMeasureSpec); + setMeasuredDimension(width, newHeightSpec); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mCurrentView.layout(l, t, l + mCurrentView.getMeasuredWidth(), + t + mCurrentView.getMeasuredHeight()); + if (mActions.getVisibility() != View.GONE) { + mActions.layout(l, mCurrentView.getMeasuredHeight(), l + mActions.getMeasuredWidth(), + mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight()); + } + } + + /** + * @hide + */ + public void showSlice(Intent intent) { + // TODO + } + + /** + * Populates this view with the {@link Slice} associated with the provided {@link Uri}. To use + * this method your app must have the permission + * {@link android.Manifest.permission#BIND_SLICE}). + * <p> + * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is + * updated when the slice identified by the provided URI changes. The lifecycle of this observer + * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}. + * To unregister this observer outside of that you can call {@link #clearSlice}. + * + * @return true if the a slice was found for the provided uri. + * @see #clearSlice + */ + public boolean setSlice(@NonNull Uri sliceUri) { + Preconditions.checkNotNull(sliceUri, + "Uri cannot be null, to remove the slice use clearSlice()"); + if (sliceUri == null) { + clearSlice(); + return false; + } + validate(sliceUri); + Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri); + if (s != null) { + mObserver = new SliceObserver(new Handler(Looper.getMainLooper())); + if (isAttachedToWindow()) { + registerSlice(sliceUri); + } + showSlice(s); + } + return s != null; + } + + /** + * Populates this view to the provided {@link Slice}. + * <p> + * This does not register a content observer on the URI that the slice is backed by so it will + * not update if the content changes. To have the view update when the content changes use + * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require + * any special permissions. + */ + public void showSlice(@NonNull Slice slice) { + Preconditions.checkNotNull(slice, + "Slice cannot be null, to remove the slice use clearSlice()"); + clearSlice(); + mCurrentSlice = slice; + reinflate(); + } + + /** + * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is + * done automatically during {@link #onDetachedFromWindow()}. + * <p> + * It is safe to call this method multiple times. + */ + public void clearSlice() { + mCurrentSlice = null; + if (mObserver != null) { + getContext().getContentResolver().unregisterContentObserver(mObserver); + mObserver = null; + } + } + + /** + * Set the mode this view should present in. + */ + public void setMode(@SliceMode String mode) { + setMode(mode, false /* animate */); + } + + /** + * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}. + */ + public void setScrollable(boolean isScrollable) { + mIsScrollable = isScrollable; + reinflate(); + } + + /** + * @hide + */ + public void setMode(@SliceMode String mode, boolean animate) { + if (animate) { + Log.e(TAG, "Animation not supported yet"); + } + mMode = mode; + reinflate(); + } + + /** + * @return the mode this view is presenting in. + */ + public @SliceMode String getMode() { + if (mMode.equals(MODE_AUTO)) { + return MODE_LARGE; + } + return mMode; + } + + /** + * @hide + * + * Whether this view should show a row of actions with it. + */ + public void setShowActionRow(boolean show) { + mShowActions = show; + reinflate(); + } + + private SliceModeView createView(String mode) { + switch (mode) { + case MODE_SHORTCUT: + return new ShortcutView(getContext()); + case MODE_SMALL: + return new SmallTemplateView(getContext()); + } + return new LargeTemplateView(getContext()); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mObserver != null) { + getContext().getContentResolver().unregisterContentObserver(mObserver); + mObserver = null; + } + } + + private void registerSlice(Uri sliceUri) { + if (sliceUri == null || mObserver == null) { + return; + } + mContext.getContentResolver().registerContentObserver(sliceUri, + false /* notifyForDescendants */, mObserver); + } + + private void reinflate() { + if (mCurrentSlice == null) { + return; + } + // TODO: Smarter mapping here from one state to the next. + SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR); + List<SliceItem> items = mCurrentSlice.getItems(); + SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE, + Slice.HINT_ACTIONS, + Slice.HINT_ALT); + String mode = getMode(); + if (!mode.equals(mCurrentView.getMode())) { + removeAllViews(); + mCurrentView = createView(mode); + addView(mCurrentView, getChildLp(mCurrentView)); + addView(mActions, getChildLp(mActions)); + } + if (mode.equals(MODE_LARGE)) { + ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable); + } + if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) { + mCurrentView.setVisibility(View.VISIBLE); + mCurrentView.setSlice(mCurrentSlice); + } else { + mCurrentView.setVisibility(View.GONE); + } + + boolean showActions = mShowActions && actionRow != null + && !mode.equals(MODE_SHORTCUT); + if (showActions) { + mActions.setActions(actionRow, color); + mActions.setVisibility(View.VISIBLE); + } else { + mActions.setVisibility(View.GONE); + } + } + + private LayoutParams getChildLp(View child) { + if (child instanceof ShortcutView) { + return new LayoutParams(mShortcutSize, mShortcutSize); + } else { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + } + + private static void validate(Uri sliceUri) { + if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) { + throw new RuntimeException("Invalid uri " + sliceUri); + } + if (sliceUri.getPathSegments().size() == 0) { + throw new RuntimeException("Invalid uri " + sliceUri); + } + } + + private class SliceObserver extends ContentObserver { + SliceObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + this.onChange(selfChange, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + Slice s = Slice.bindSlice(mContext.getContentResolver(), uri); + mCurrentSlice = s; + reinflate(); + } + } +} diff --git a/android/app/slice/views/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java index 19e8e7c9..03669983 100644 --- a/android/app/slice/views/SliceViewUtil.java +++ b/android/app/slice/widget/SliceViewUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import android.annotation.ColorInt; import android.content.Context; diff --git a/android/app/slice/views/SmallTemplateView.java b/android/app/slice/widget/SmallTemplateView.java index 42b2d213..1c4c5df2 100644 --- a/android/app/slice/views/SmallTemplateView.java +++ b/android/app/slice/widget/SmallTemplateView.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package android.app.slice.views; +package android.app.slice.widget; import android.app.PendingIntent.CanceledException; import android.app.slice.Slice; import android.app.slice.SliceItem; import android.app.slice.SliceQuery; -import android.app.slice.views.LargeSliceAdapter.SliceListView; -import android.app.slice.views.SliceView.SliceModeView; +import android.app.slice.widget.LargeSliceAdapter.SliceListView; +import android.app.slice.widget.SliceView.SliceModeView; import android.content.Context; import android.os.AsyncTask; import android.view.View; diff --git a/android/app/usage/AppStandby.java b/android/app/usage/AppStandby.java new file mode 100644 index 00000000..6f9fc2fa --- /dev/null +++ b/android/app/usage/AppStandby.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 android.app.usage; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets + * that affect how frequently they can run in the background or perform other battery-consuming + * actions. Buckets will be assigned based on how frequently or when the system thinks the user + * is likely to use the app. + * @hide + */ +public class AppStandby { + + /** The app was used very recently, currently in use or likely to be used very soon. */ + public static final int STANDBY_BUCKET_ACTIVE = 0; + + // Leave some gap in case we want to increase the number of buckets + + /** The app was used recently and/or likely to be used in the next few hours */ + public static final int STANDBY_BUCKET_WORKING_SET = 3; + + // Leave some gap in case we want to increase the number of buckets + + /** The app was used in the last few days and/or likely to be used in the next few days */ + public static final int STANDBY_BUCKET_FREQUENT = 6; + + // Leave some gap in case we want to increase the number of buckets + + /** The app has not be used for several days and/or is unlikely to be used for several days */ + public static final int STANDBY_BUCKET_RARE = 9; + + // Leave some gap in case we want to increase the number of buckets + + /** The app has never been used. */ + public static final int STANDBY_BUCKET_NEVER = 12; + + /** Reason for bucketing -- default initial state */ + public static final String REASON_DEFAULT = "default"; + + /** Reason for bucketing -- timeout */ + public static final String REASON_TIMEOUT = "timeout"; + + /** Reason for bucketing -- usage */ + public static final String REASON_USAGE = "usage"; + + /** Reason for bucketing -- forced by user / shell command */ + public static final String REASON_FORCED = "forced"; + + /** + * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will + * be appended. + */ + public static final String REASON_PREDICTED = "predicted"; + + @IntDef(flag = false, value = { + STANDBY_BUCKET_ACTIVE, + STANDBY_BUCKET_WORKING_SET, + STANDBY_BUCKET_FREQUENT, + STANDBY_BUCKET_RARE, + STANDBY_BUCKET_NEVER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StandbyBuckets {} +} diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java index fd579fce..c827432a 100644 --- a/android/app/usage/UsageStatsManager.java +++ b/android/app/usage/UsageStatsManager.java @@ -19,6 +19,7 @@ package android.app.usage; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.usage.AppStandby.StandbyBuckets; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; @@ -247,6 +248,29 @@ public final class UsageStatsManager { } /** + * @hide + */ + public @StandbyBuckets int getAppStandbyBucket(String packageName) { + try { + return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(), + mContext.getUserId()); + } catch (RemoteException e) { + } + return AppStandby.STANDBY_BUCKET_ACTIVE; + } + + /** + * @hide + */ + public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) { + try { + mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId()); + } catch (RemoteException e) { + // Nothing to do + } + } + + /** * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app * receiving a high priority message to be able to access the network and acquire wakelocks diff --git a/android/appwidget/AppWidgetHostView.java b/android/appwidget/AppWidgetHostView.java index dc9970a7..ab0eb92e 100644 --- a/android/appwidget/AppWidgetHostView.java +++ b/android/appwidget/AppWidgetHostView.java @@ -19,8 +19,6 @@ package android.appwidget; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherApps; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Color; @@ -66,11 +64,8 @@ public class AppWidgetHostView extends FrameLayout { // When we're inflating the initialLayout for a AppWidget, we only allow // views that are allowed in RemoteViews. - static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() { - public boolean onLoadClass(Class clazz) { - return clazz.isAnnotationPresent(RemoteViews.RemoteView.class); - } - }; + private static final LayoutInflater.Filter INFLATER_FILTER = + (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); Context mContext; Context mRemoteContext; @@ -136,13 +131,19 @@ public class AppWidgetHostView extends FrameLayout { mAppWidgetId = appWidgetId; mInfo = info; + // We add padding to the AppWidgetHostView if necessary + Rect padding = getDefaultPadding(); + setPadding(padding.left, padding.top, padding.right, padding.bottom); + // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for // a widget, eg. for some widgets in safe mode. if (info != null) { - // We add padding to the AppWidgetHostView if necessary - Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null); - setPadding(padding.left, padding.top, padding.right, padding.bottom); - updateContentDescription(info); + String description = info.loadLabel(getContext().getPackageManager()); + if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) { + description = Resources.getSystem().getString( + com.android.internal.R.string.suspended_widget_accessibility, description); + } + setContentDescription(description); } } @@ -164,23 +165,23 @@ public class AppWidgetHostView extends FrameLayout { */ public static Rect getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding) { - PackageManager packageManager = context.getPackageManager(); - ApplicationInfo appInfo; + ApplicationInfo appInfo = null; + try { + appInfo = context.getPackageManager().getApplicationInfo(component.getPackageName(), 0); + } catch (NameNotFoundException e) { + // if we can't find the package, ignore + } + return getDefaultPaddingForWidget(context, appInfo, padding); + } + private static Rect getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo, + Rect padding) { if (padding == null) { padding = new Rect(0, 0, 0, 0); } else { padding.set(0, 0, 0, 0); } - - try { - appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0); - } catch (NameNotFoundException e) { - // if we can't find the package, return 0 padding - return padding; - } - - if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if (appInfo != null && appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { Resources r = context.getResources(); padding.left = r.getDimensionPixelSize(com.android.internal. R.dimen.default_app_widget_padding_left); @@ -194,6 +195,11 @@ public class AppWidgetHostView extends FrameLayout { return padding; } + private Rect getDefaultPadding() { + return getDefaultPaddingForWidget(mContext, + mInfo == null ? null : mInfo.providerInfo.applicationInfo, null); + } + public int getAppWidgetId() { return mAppWidgetId; } @@ -284,10 +290,7 @@ public class AppWidgetHostView extends FrameLayout { newOptions = new Bundle(); } - Rect padding = new Rect(); - if (mInfo != null) { - padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding); - } + Rect padding = getDefaultPadding(); float density = getResources().getDisplayMetrics().density; int xPaddingDips = (int) ((padding.left + padding.right) / density); @@ -361,7 +364,7 @@ public class AppWidgetHostView extends FrameLayout { * initial layout. */ void resetAppWidget(AppWidgetProviderInfo info) { - mInfo = info; + setAppWidget(mAppWidgetId, info); mViewMode = VIEW_MODE_NOINIT; updateAppWidget(null); } @@ -433,7 +436,6 @@ public class AppWidgetHostView extends FrameLayout { } applyContent(content, recycled, exception); - updateContentDescription(mInfo); } private void applyContent(View content, boolean recycled, Exception exception) { @@ -460,27 +462,6 @@ public class AppWidgetHostView extends FrameLayout { } } - private void updateContentDescription(AppWidgetProviderInfo info) { - if (info != null) { - LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class); - ApplicationInfo appInfo = null; - try { - appInfo = launcherApps.getApplicationInfo( - info.provider.getPackageName(), 0, info.getProfile()); - } catch (NameNotFoundException e) { - // ignore -- use null. - } - if (appInfo != null && - (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) { - setContentDescription( - Resources.getSystem().getString( - com.android.internal.R.string.suspended_widget_accessibility, info.label)); - } else { - setContentDescription(info.label); - } - } - } - private void inflateAsync(RemoteViews remoteViews) { // Prepare a local reference to the remote Context so we're ready to // inflate any requested LayoutParams. @@ -614,7 +595,7 @@ public class AppWidgetHostView extends FrameLayout { LayoutInflater inflater = (LayoutInflater) theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater = inflater.cloneInContext(theirContext); - inflater.setFilter(sInflaterFilter); + inflater.setFilter(INFLATER_FILTER); AppWidgetManager manager = AppWidgetManager.getInstance(mContext); Bundle options = manager.getAppWidgetOptions(mAppWidgetId); diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java index f1352446..1ddcb1a9 100644 --- a/android/arch/lifecycle/ComputableLiveData.java +++ b/android/arch/lifecycle/ComputableLiveData.java @@ -1,9 +1,136 @@ -//ComputableLiveData interface for tests +/* + * 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 android.arch.lifecycle; -import android.arch.lifecycle.LiveData; + +import android.arch.core.executor.ArchTaskExecutor; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; +import android.support.annotation.VisibleForTesting; +import android.support.annotation.WorkerThread; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A LiveData class that can be invalidated & computed on demand. + * <p> + * This is an internal class for now, might be public if we see the necessity. + * + * @param <T> The type of the live data + * @hide internal + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public abstract class ComputableLiveData<T> { - public ComputableLiveData(){} - abstract protected T compute(); - public LiveData<T> getLiveData() {return null;} - public void invalidate() {} + + private final LiveData<T> mLiveData; + + private AtomicBoolean mInvalid = new AtomicBoolean(true); + private AtomicBoolean mComputing = new AtomicBoolean(false); + + /** + * Creates a computable live data which is computed when there are active observers. + * <p> + * It can also be invalidated via {@link #invalidate()} which will result in a call to + * {@link #compute()} if there are active observers (or when they start observing) + */ + @SuppressWarnings("WeakerAccess") + public ComputableLiveData() { + mLiveData = new LiveData<T>() { + @Override + protected void onActive() { + // TODO if we make this class public, we should accept an executor + ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); + } + }; + } + + /** + * Returns the LiveData managed by this class. + * + * @return A LiveData that is controlled by ComputableLiveData. + */ + @SuppressWarnings("WeakerAccess") + @NonNull + public LiveData<T> getLiveData() { + return mLiveData; + } + + @VisibleForTesting + final Runnable mRefreshRunnable = new Runnable() { + @WorkerThread + @Override + public void run() { + boolean computed; + do { + computed = false; + // compute can happen only in 1 thread but no reason to lock others. + if (mComputing.compareAndSet(false, true)) { + // as long as it is invalid, keep computing. + try { + T value = null; + while (mInvalid.compareAndSet(true, false)) { + computed = true; + value = compute(); + } + if (computed) { + mLiveData.postValue(value); + } + } finally { + // release compute lock + mComputing.set(false); + } + } + // check invalid after releasing compute lock to avoid the following scenario. + // Thread A runs compute() + // Thread A checks invalid, it is false + // Main thread sets invalid to true + // Thread B runs, fails to acquire compute lock and skips + // Thread A releases compute lock + // We've left invalid in set state. The check below recovers. + } while (computed && mInvalid.get()); + } + }; + + // invalidation check always happens on the main thread + @VisibleForTesting + final Runnable mInvalidationRunnable = new Runnable() { + @MainThread + @Override + public void run() { + boolean isActive = mLiveData.hasActiveObservers(); + if (mInvalid.compareAndSet(false, true)) { + if (isActive) { + // TODO if we make this class public, we should accept an executor. + ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable); + } + } + } + }; + + /** + * Invalidates the LiveData. + * <p> + * When there are active observers, this will trigger a call to {@link #compute()}. + */ + public void invalidate() { + ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable); + } + + @SuppressWarnings("WeakerAccess") + @WorkerThread + protected abstract T compute(); } diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java index 3aea6acb..5b09c32f 100644 --- a/android/arch/lifecycle/LiveData.java +++ b/android/arch/lifecycle/LiveData.java @@ -1,4 +1,410 @@ -//LiveData interface for tests +/* + * 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 android.arch.lifecycle; -public class LiveData<T> { + +import static android.arch.lifecycle.Lifecycle.State.DESTROYED; +import static android.arch.lifecycle.Lifecycle.State.STARTED; + +import android.arch.core.executor.ArchTaskExecutor; +import android.arch.core.internal.SafeIterableMap; +import android.arch.lifecycle.Lifecycle.State; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Iterator; +import java.util.Map; + +/** + * LiveData is a data holder class that can be observed within a given lifecycle. + * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and + * this observer will be notified about modifications of the wrapped data only if the paired + * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is + * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via + * {@link #observeForever(Observer)} is considered as always active and thus will be always notified + * about modifications. For those observers, you should manually call + * {@link #removeObserver(Observer)}. + * + * <p> An observer added with a Lifecycle will be automatically removed if the corresponding + * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for + * activities and fragments where they can safely observe LiveData and not worry about leaks: + * they will be instantly unsubscribed when they are destroyed. + * + * <p> + * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods + * to get notified when number of active {@link Observer}s change between 0 and 1. + * This allows LiveData to release any heavy resources when it does not have any Observers that + * are actively observing. + * <p> + * This class is designed to hold individual data fields of {@link ViewModel}, + * but can also be used for sharing data between different modules in your application + * in a decoupled fashion. + * + * @param <T> The type of data held by this instance + * @see ViewModel + */ +@SuppressWarnings({"WeakerAccess", "unused"}) +// TODO: Thread checks are too strict right now, we may consider automatically moving them to main +// thread. +public abstract class LiveData<T> { + private final Object mDataLock = new Object(); + static final int START_VERSION = -1; + private static final Object NOT_SET = new Object(); + + private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() { + + private LifecycleRegistry mRegistry = init(); + + private LifecycleRegistry init() { + LifecycleRegistry registry = new LifecycleRegistry(this); + registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + registry.handleLifecycleEvent(Lifecycle.Event.ON_START); + registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME); + return registry; + } + + @Override + public Lifecycle getLifecycle() { + return mRegistry; + } + }; + + private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers = + new SafeIterableMap<>(); + + // how many observers are in active state + private int mActiveCount = 0; + private volatile Object mData = NOT_SET; + // when setData is called, we set the pending data and actual data swap happens on the main + // thread + private volatile Object mPendingData = NOT_SET; + private int mVersion = START_VERSION; + + private boolean mDispatchingValue; + @SuppressWarnings("FieldCanBeLocal") + private boolean mDispatchInvalidated; + private final Runnable mPostValueRunnable = new Runnable() { + @Override + public void run() { + Object newValue; + synchronized (mDataLock) { + newValue = mPendingData; + mPendingData = NOT_SET; + } + //noinspection unchecked + setValue((T) newValue); + } + }; + + private void considerNotify(LifecycleBoundObserver observer) { + if (!observer.active) { + return; + } + // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet. + // + // we still first check observer.active to keep it as the entrance for events. So even if + // the observer moved to an active state, if we've not received that event, we better not + // notify for a more predictable notification order. + if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) { + observer.activeStateChanged(false); + return; + } + if (observer.lastVersion >= mVersion) { + return; + } + observer.lastVersion = mVersion; + //noinspection unchecked + observer.observer.onChanged((T) mData); + } + + private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) { + if (mDispatchingValue) { + mDispatchInvalidated = true; + return; + } + mDispatchingValue = true; + do { + mDispatchInvalidated = false; + if (initiator != null) { + considerNotify(initiator); + initiator = null; + } else { + for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator = + mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { + considerNotify(iterator.next().getValue()); + if (mDispatchInvalidated) { + break; + } + } + } + } while (mDispatchInvalidated); + mDispatchingValue = false; + } + + /** + * Adds the given observer to the observers list within the lifespan of the given + * owner. The events are dispatched on the main thread. If LiveData already has data + * set, it will be delivered to the observer. + * <p> + * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED} + * or {@link Lifecycle.State#RESUMED} state (active). + * <p> + * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will + * automatically be removed. + * <p> + * When data changes while the {@code owner} is not active, it will not receive any updates. + * If it becomes active again, it will receive the last available data automatically. + * <p> + * LiveData keeps a strong reference to the observer and the owner as long as the + * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to + * the observer & the owner. + * <p> + * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData + * ignores the call. + * <p> + * If the given owner, observer tuple is already in the list, the call is ignored. + * If the observer is already in the list with another owner, LiveData throws an + * {@link IllegalArgumentException}. + * + * @param owner The LifecycleOwner which controls the observer + * @param observer The observer that will receive the events + */ + @MainThread + public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) { + if (owner.getLifecycle().getCurrentState() == DESTROYED) { + // ignore + return; + } + LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); + LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper); + if (existing != null && existing.owner != wrapper.owner) { + throw new IllegalArgumentException("Cannot add the same observer" + + " with different lifecycles"); + } + if (existing != null) { + return; + } + owner.getLifecycle().addObserver(wrapper); + } + + /** + * Adds the given observer to the observers list. This call is similar to + * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which + * is always active. This means that the given observer will receive all events and will never + * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop + * observing this LiveData. + * While LiveData has one of such observers, it will be considered + * as active. + * <p> + * If the observer was already added with an owner to this LiveData, LiveData throws an + * {@link IllegalArgumentException}. + * + * @param observer The observer that will receive the events + */ + @MainThread + public void observeForever(@NonNull Observer<T> observer) { + observe(ALWAYS_ON, observer); + } + + /** + * Removes the given observer from the observers list. + * + * @param observer The Observer to receive events. + */ + @MainThread + public void removeObserver(@NonNull final Observer<T> observer) { + assertMainThread("removeObserver"); + LifecycleBoundObserver removed = mObservers.remove(observer); + if (removed == null) { + return; + } + removed.owner.getLifecycle().removeObserver(removed); + removed.activeStateChanged(false); + } + + /** + * Removes all observers that are tied to the given {@link LifecycleOwner}. + * + * @param owner The {@code LifecycleOwner} scope for the observers to be removed. + */ + @MainThread + public void removeObservers(@NonNull final LifecycleOwner owner) { + assertMainThread("removeObservers"); + for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) { + if (entry.getValue().owner == owner) { + removeObserver(entry.getKey()); + } + } + } + + /** + * Posts a task to a main thread to set the given value. So if you have a following code + * executed in the main thread: + * <pre class="prettyprint"> + * liveData.postValue("a"); + * liveData.setValue("b"); + * </pre> + * The value "b" would be set at first and later the main thread would override it with + * the value "a". + * <p> + * If you called this method multiple times before a main thread executed a posted task, only + * the last value would be dispatched. + * + * @param value The new value + */ + protected void postValue(T value) { + boolean postTask; + synchronized (mDataLock) { + postTask = mPendingData == NOT_SET; + mPendingData = value; + } + if (!postTask) { + return; + } + ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); + } + + /** + * Sets the value. If there are active observers, the value will be dispatched to them. + * <p> + * This method must be called from the main thread. If you need set a value from a background + * thread, you can use {@link #postValue(Object)} + * + * @param value The new value + */ + @MainThread + protected void setValue(T value) { + assertMainThread("setValue"); + mVersion++; + mData = value; + dispatchingValue(null); + } + + /** + * Returns the current value. + * Note that calling this method on a background thread does not guarantee that the latest + * value set will be received. + * + * @return the current value + */ + @Nullable + public T getValue() { + Object data = mData; + if (data != NOT_SET) { + //noinspection unchecked + return (T) data; + } + return null; + } + + int getVersion() { + return mVersion; + } + + /** + * Called when the number of active observers change to 1 from 0. + * <p> + * This callback can be used to know that this LiveData is being used thus should be kept + * up to date. + */ + protected void onActive() { + + } + + /** + * Called when the number of active observers change from 1 to 0. + * <p> + * This does not mean that there are no observers left, there may still be observers but their + * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED} + * (like an Activity in the back stack). + * <p> + * You can check if there are observers via {@link #hasObservers()}. + */ + protected void onInactive() { + + } + + /** + * Returns true if this LiveData has observers. + * + * @return true if this LiveData has observers + */ + public boolean hasObservers() { + return mObservers.size() > 0; + } + + /** + * Returns true if this LiveData has active observers. + * + * @return true if this LiveData has active observers + */ + public boolean hasActiveObservers() { + return mActiveCount > 0; + } + + class LifecycleBoundObserver implements GenericLifecycleObserver { + public final LifecycleOwner owner; + public final Observer<T> observer; + public boolean active; + public int lastVersion = START_VERSION; + + LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) { + this.owner = owner; + this.observer = observer; + } + + @Override + public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { + if (owner.getLifecycle().getCurrentState() == DESTROYED) { + removeObserver(observer); + return; + } + // immediately set active state, so we'd never dispatch anything to inactive + // owner + activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState())); + } + + void activeStateChanged(boolean newActive) { + if (newActive == active) { + return; + } + active = newActive; + boolean wasInactive = LiveData.this.mActiveCount == 0; + LiveData.this.mActiveCount += active ? 1 : -1; + if (wasInactive && active) { + onActive(); + } + if (LiveData.this.mActiveCount == 0 && !active) { + onInactive(); + } + if (active) { + dispatchingValue(this); + } + } + } + + static boolean isActiveState(State state) { + return state.isAtLeast(STARTED); + } + + private void assertMainThread(String methodName) { + if (!ArchTaskExecutor.getInstance().isMainThread()) { + throw new IllegalStateException("Cannot invoke " + methodName + " on a background" + + " thread"); + } + } } diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java index be9da200..414c4ffe 100644 --- a/android/arch/paging/ContiguousDataSource.java +++ b/android/arch/paging/ContiguousDataSource.java @@ -18,55 +18,44 @@ package android.arch.paging; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; -import android.support.annotation.WorkerThread; import java.util.List; -/** @hide */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> { +abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> { @Override boolean isContiguous() { return true; } - void loadInitial(Key key, int pageSize, boolean enablePlaceholders, - PageResult.Receiver<Key, Value> receiver) { - NullPaddedList<Value> initial = loadInitial(key, pageSize, enablePlaceholders); - if (initial != null) { - receiver.onPageResult(new PageResult<>( - PageResult.INIT, - new Page<Key, Value>(initial.mList), - initial.getLeadingNullCount(), - initial.getTrailingNullCount(), - initial.getPositionOffset())); - } else { - receiver.onPageResult(new PageResult<Key, Value>( - PageResult.INIT, null, 0, 0, 0)); - } - } + abstract void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders, + @NonNull PageResult.Receiver<Key, Value> receiver); void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize, - PageResult.Receiver<Key, Value> receiver) { - List<Value> list = loadAfter(currentEndIndex, currentEndItem, pageSize); - - Page<Key, Value> page = list != null - ? new Page<Key, Value>(list) : null; - - receiver.postOnPageResult(new PageResult<>( - PageResult.APPEND, page, 0, 0, 0)); + @NonNull PageResult.Receiver<Key, Value> receiver) { + if (!isInvalid()) { + List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize); + + if (list != null && !isInvalid()) { + receiver.postOnPageResult(new PageResult<>( + PageResult.APPEND, new Page<Key, Value>(list), 0, 0, 0)); + return; + } + } + receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.APPEND)); } void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize, - PageResult.Receiver<Key, Value> receiver) { - List<Value> list = loadBefore(currentBeginIndex, currentBeginItem, pageSize); - - Page<Key, Value> page = list != null - ? new Page<Key, Value>(list) : null; - - receiver.postOnPageResult(new PageResult<>( - PageResult.PREPEND, page, 0, 0, 0)); + @NonNull PageResult.Receiver<Key, Value> receiver) { + if (!isInvalid()) { + List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize); + + if (list != null && !isInvalid()) { + receiver.postOnPageResult(new PageResult<>( + PageResult.PREPEND, new Page<Key, Value>(list), 0, 0, 0)); + return; + } + } + receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.PREPEND)); } /** @@ -84,44 +73,4 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V @Nullable abstract List<Value> loadBeforeImpl(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize); - - /** @hide */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @WorkerThread - @Nullable - public abstract NullPaddedList<Value> loadInitial( - Key key, int initialLoadSize, boolean enablePlaceholders); - - /** @hide */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @WorkerThread - @Nullable - public final List<Value> loadAfter(int currentEndIndex, - @NonNull Value currentEndItem, int pageSize) { - if (isInvalid()) { - return null; - } - List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize); - if (isInvalid()) { - return null; - } - return list; - } - - /** @hide */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @WorkerThread - @Nullable - public final List<Value> loadBefore(int currentBeginIndex, - @NonNull Value currentBeginItem, int pageSize) { - if (isInvalid()) { - return null; - } - List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize); - if (isInvalid()) { - return null; - } - return list; - - } } diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java index 2a5cd42f..cdff391d 100644 --- a/android/arch/paging/ContiguousPagedList.java +++ b/android/arch/paging/ContiguousPagedList.java @@ -47,7 +47,9 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal }); } - @MainThread + // Creation thread for initial synchronous load, otherwise main thread + // Safe to access main thread only state - no other thread has reference during construction + @AnyThread @Override public void onPageResult(@NonNull PageResult<K, V> pageResult) { if (pageResult.page == null) { @@ -70,6 +72,17 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal } else if (pageResult.type == PageResult.PREPEND) { mKeyedStorage.prependPage(page, ContiguousPagedList.this); } + + if (mBoundaryCallback != null) { + boolean deferEmpty = mStorage.size() == 0; + boolean deferBegin = !deferEmpty + && pageResult.type == PageResult.PREPEND + && pageResult.page.items.size() == 0; + boolean deferEnd = !deferEmpty + && pageResult.type == PageResult.APPEND + && pageResult.page.items.size() == 0; + deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); + } } }; @@ -77,9 +90,11 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal @NonNull ContiguousDataSource<K, V> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, + @Nullable BoundaryCallback<V> boundaryCallback, @NonNull Config config, final @Nullable K key) { - super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config); + super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, + boundaryCallback, config); mDataSource = dataSource; // blocking init just triggers the initial load on the construction thread - @@ -168,7 +183,7 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset(); // safe to access first item here - mStorage can't be empty if we're prepending - final V item = mStorage.getFirstContiguousItem(); + final V item = mStorage.getFirstLoadedItem(); mBackgroundThreadExecutor.execute(new Runnable() { @Override public void run() { @@ -191,7 +206,7 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset(); // safe to access first item here - mStorage can't be empty if we're appending - final V item = mStorage.getLastContiguousItem(); + final V item = mStorage.getLastLoadedItem(); mBackgroundThreadExecutor.execute(new Runnable() { @Override public void run() { @@ -234,6 +249,8 @@ class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Cal // finally dispatch callbacks, after prepend may have already been scheduled notifyChanged(leadingNulls, changedCount); notifyInserted(0, addedCount); + + offsetBoundaryAccessIndices(addedCount); } @MainThread diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java index 524e570a..ff44521e 100644 --- a/android/arch/paging/DataSource.java +++ b/android/arch/paging/DataSource.java @@ -48,6 +48,10 @@ import java.util.concurrent.atomic.AtomicBoolean; @SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety public abstract class DataSource<Key, Value> { + public interface Factory<Key, Value> { + DataSource<Key, Value> create(); + } + // Since we currently rely on implementation details of two implementations, // prevent external subclassing, except through exposed subclasses DataSource() { diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java index 0d452946..3214a4ef 100644 --- a/android/arch/paging/KeyedDataSource.java +++ b/android/arch/paging/KeyedDataSource.java @@ -19,7 +19,6 @@ package android.arch.paging; import android.support.annotation.AnyThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; import android.support.annotation.WorkerThread; import java.util.ArrayList; @@ -124,9 +123,33 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K return list; } + + @Override + void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders, + @NonNull PageResult.Receiver<Key, Value> receiver) { + + PageResult<Key, Value> pageResult = + loadInitialInternal(key, initialLoadSize, enablePlaceholders); + if (pageResult == null) { + // loading failed, return empty page + receiver.onPageResult(new PageResult<Key, Value>(PageResult.INIT)); + } else { + receiver.onPageResult(pageResult); + } + } + + /** + * Try initial load, and either return the successful initial load to the receiver, + * or null if unsuccessful. + */ @Nullable - private NullPaddedList<Value> loadInitialInternal( + private PageResult<Key, Value> loadInitialInternal( @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) { + // check if invalid at beginning, and before returning a valid list + if (isInvalid()) { + return null; + } + List<Value> list; if (key == null) { // no key, so load initial. @@ -171,9 +194,14 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K } } + final Page<Key, Value> page = new Page<>(list); + if (list.isEmpty()) { - // wasn't able to load any items, so publish an unpadded empty list. - return new NullPaddedList<>(0, Collections.<Value>emptyList()); + if (isInvalid()) { + return null; + } + // wasn't able to load any items, but not invalid - return an empty page. + return new PageResult<>(PageResult.INIT, page, 0, 0, 0); } int itemsBefore = COUNT_UNDEFINED; @@ -181,31 +209,21 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K if (enablePlaceholders) { itemsBefore = countItemsBefore(getKey(list.get(0))); itemsAfter = countItemsAfter(getKey(list.get(list.size() - 1))); - if (isInvalid()) { - return null; - } - } - if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) { - return new NullPaddedList<>(0, list, 0); - } else { - return new NullPaddedList<>(itemsBefore, list, itemsAfter); } - } - /** @hide */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @WorkerThread - @Override - public NullPaddedList<Value> loadInitial( - @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) { if (isInvalid()) { return null; } - NullPaddedList<Value> list = loadInitialInternal(key, initialLoadSize, enablePlaceholders); - if (list == null || isInvalid()) { - return null; + if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) { + itemsBefore = 0; + itemsAfter = 0; } - return list; + return new PageResult<>( + PageResult.INIT, + page, + itemsBefore, + itemsAfter, + 0); } /** diff --git a/android/arch/paging/ListDataSource.java b/android/arch/paging/ListDataSource.java new file mode 100644 index 00000000..d3a171e5 --- /dev/null +++ b/android/arch/paging/ListDataSource.java @@ -0,0 +1,39 @@ +/* + * 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 android.arch.paging; + +import java.util.ArrayList; +import java.util.List; + +public class ListDataSource<T> extends TiledDataSource<T> { + private final List<T> mList; + + public ListDataSource(List<T> list) { + mList = new ArrayList<>(list); + } + + @Override + public int countItems() { + return mList.size(); + } + + @Override + public List<T> loadRange(int startPosition, int count) { + int endExclusive = Math.min(mList.size(), startPosition + count); + return mList.subList(startPosition, endExclusive); + } +} diff --git a/android/arch/paging/LivePagedListBuilder.java b/android/arch/paging/LivePagedListBuilder.java new file mode 100644 index 00000000..ee1810b5 --- /dev/null +++ b/android/arch/paging/LivePagedListBuilder.java @@ -0,0 +1,159 @@ +/* + * 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 android.arch.paging; + +import android.arch.core.executor.ArchTaskExecutor; +import android.arch.lifecycle.ComputableLiveData; +import android.arch.lifecycle.LiveData; +import android.support.annotation.AnyThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.concurrent.Executor; + +public class LivePagedListBuilder<Key, Value> { + private Key mInitialLoadKey; + private PagedList.Config mConfig; + private DataSource.Factory<Key, Value> mDataSourceFactory; + private PagedList.BoundaryCallback mBoundaryCallback; + private Executor mMainThreadExecutor; + private Executor mBackgroundThreadExecutor; + + @SuppressWarnings("WeakerAccess") + @NonNull + public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) { + mInitialLoadKey = key; + return this; + } + + @SuppressWarnings("WeakerAccess") + @NonNull + public LivePagedListBuilder<Key, Value> setPagingConfig(@NonNull PagedList.Config config) { + mConfig = config; + return this; + } + + @SuppressWarnings("WeakerAccess") + @NonNull + public LivePagedListBuilder<Key, Value> setPagingConfig(int pageSize) { + mConfig = new PagedList.Config.Builder().setPageSize(pageSize).build(); + return this; + } + + @NonNull + public LivePagedListBuilder<Key, Value> setDataSourceFactory( + @NonNull DataSource.Factory<Key, Value> dataSourceFactory) { + mDataSourceFactory = dataSourceFactory; + return this; + } + + @SuppressWarnings("unused") + @NonNull + public LivePagedListBuilder<Key, Value> setBoundaryCallback( + @Nullable PagedList.BoundaryCallback<Value> boundaryCallback) { + mBoundaryCallback = boundaryCallback; + return this; + } + + @SuppressWarnings("unused") + @NonNull + public LivePagedListBuilder<Key, Value> setMainThreadExecutor( + @NonNull Executor mainThreadExecutor) { + mMainThreadExecutor = mainThreadExecutor; + return this; + } + + @SuppressWarnings("unused") + @NonNull + public LivePagedListBuilder<Key, Value> setBackgroundThreadExecutor( + @NonNull Executor backgroundThreadExecutor) { + mBackgroundThreadExecutor = backgroundThreadExecutor; + return this; + } + + @NonNull + public LiveData<PagedList<Value>> build() { + if (mConfig == null) { + throw new IllegalArgumentException("PagedList.Config must be provided"); + } + if (mDataSourceFactory == null) { + throw new IllegalArgumentException("DataSource.Factory must be provided"); + } + if (mMainThreadExecutor == null) { + mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor(); + } + if (mBackgroundThreadExecutor == null) { + mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor(); + } + + return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory, + mMainThreadExecutor, mBackgroundThreadExecutor); + } + + @AnyThread + @NonNull + public static <Key, Value> LiveData<PagedList<Value>> create( + @Nullable final Key initialLoadKey, + @NonNull final PagedList.Config config, + @Nullable final PagedList.BoundaryCallback boundaryCallback, + @NonNull final DataSource.Factory<Key, Value> dataSourceFactory, + @NonNull final Executor mainThreadExecutor, + @NonNull final Executor backgroundThreadExecutor) { + return new ComputableLiveData<PagedList<Value>>() { + @Nullable + private PagedList<Value> mList; + @Nullable + private DataSource<Key, Value> mDataSource; + + private final DataSource.InvalidatedCallback mCallback = + new DataSource.InvalidatedCallback() { + @Override + public void onInvalidated() { + invalidate(); + } + }; + + @Override + protected PagedList<Value> compute() { + @Nullable Key initializeKey = initialLoadKey; + if (mList != null) { + //noinspection unchecked + initializeKey = (Key) mList.getLastKey(); + } + + do { + if (mDataSource != null) { + mDataSource.removeInvalidatedCallback(mCallback); + } + + mDataSource = dataSourceFactory.create(); + mDataSource.addInvalidatedCallback(mCallback); + + mList = new PagedList.Builder<Key, Value>() + .setDataSource(mDataSource) + .setMainThreadExecutor(mainThreadExecutor) + .setBackgroundThreadExecutor(backgroundThreadExecutor) + .setBoundaryCallback(boundaryCallback) + .setConfig(config) + .setInitialKey(initializeKey) + .build(); + } while (mList.isDetached()); + return mList; + } + }.getLiveData(); + } +} diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java index 07dd84bf..b7c68dd6 100644 --- a/android/arch/paging/LivePagedListProvider.java +++ b/android/arch/paging/LivePagedListProvider.java @@ -16,133 +16,5 @@ package android.arch.paging; -import android.arch.core.executor.ArchTaskExecutor; -import android.arch.lifecycle.ComputableLiveData; -import android.arch.lifecycle.LiveData; -import android.support.annotation.AnyThread; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; - -/** - * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource. - * <p> - * Return type for data-loading system of an application or library to produce a - * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the - * consumer. - * <p> - * If you're using Room, it can generate a LivePagedListProvider from a query: - * <pre> - * {@literal @}Dao - * interface UserDao { - * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") - * public abstract LivePagedListProvider<Integer, User> usersByLastName(); - * }</pre> - * In the above sample, {@code Integer} is used because it is the {@code Key} type of - * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET}, - * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer} - * here lets you pass an initial loading position as an integer. - * <p> - * In the future, Room plans to offer other key types to support paging content with a - * {@link KeyedDataSource}. - * - * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if - * you're using TiledDataSource. - * @param <Value> Data type produced by the DataSource, and held by the PagedLists. - * - * @see PagedListAdapter - * @see DataSource - * @see PagedList - */ -public abstract class LivePagedListProvider<Key, Value> { - - /** - * Construct a new data source to be wrapped in a new PagedList, which will be returned - * through the LiveData. - * - * @return The data source. - */ - @WorkerThread - protected abstract DataSource<Key, Value> createDataSource(); - - /** - * Creates a LiveData of PagedLists, given the page size. - * <p> - * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a - * {@link android.support.v7.widget.RecyclerView}. - * - * @param initialLoadKey Initial key used to load initial data from the data source. - * @param pageSize Page size defining how many items are loaded from a data source at a time. - * Recommended to be multiple times the size of item displayed at once. - * - * @return The LiveData of PagedLists. - */ - @AnyThread - @NonNull - public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) { - return create(initialLoadKey, - new PagedList.Config.Builder() - .setPageSize(pageSize) - .build()); - } - - /** - * Creates a LiveData of PagedLists, given the PagedList.Config. - * <p> - * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a - * {@link android.support.v7.widget.RecyclerView}. - * - * @param initialLoadKey Initial key to pass to the data source to initialize data with. - * @param config PagedList.Config to use with created PagedLists. This specifies how the - * lists will load data. - * - * @return The LiveData of PagedLists. - */ - @AnyThread - @NonNull - public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey, - final PagedList.Config config) { - return new ComputableLiveData<PagedList<Value>>() { - @Nullable - private PagedList<Value> mList; - @Nullable - private DataSource<Key, Value> mDataSource; - - private final DataSource.InvalidatedCallback mCallback = - new DataSource.InvalidatedCallback() { - @Override - public void onInvalidated() { - invalidate(); - } - }; - - @Override - protected PagedList<Value> compute() { - @Nullable Key initializeKey = initialLoadKey; - if (mList != null) { - //noinspection unchecked - initializeKey = (Key) mList.getLastKey(); - } - - do { - if (mDataSource != null) { - mDataSource.removeInvalidatedCallback(mCallback); - } - - mDataSource = createDataSource(); - mDataSource.addInvalidatedCallback(mCallback); - - mList = new PagedList.Builder<Key, Value>() - .setDataSource(mDataSource) - .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor()) - .setBackgroundThreadExecutor( - ArchTaskExecutor.getIOThreadExecutor()) - .setConfig(config) - .setInitialKey(initializeKey) - .build(); - } while (mList.isDetached()); - return mList; - } - }.getLiveData(); - } -} +abstract public class LivePagedListProvider<K, T> { +}
\ No newline at end of file diff --git a/android/arch/paging/NullPaddedList.java b/android/arch/paging/NullPaddedList.java deleted file mode 100644 index c7b0b231..00000000 --- a/android/arch/paging/NullPaddedList.java +++ /dev/null @@ -1,140 +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 android.arch.paging; - -import android.support.annotation.RestrictTo; - -import java.util.AbstractList; -import java.util.List; - -/** - * NullPaddedList is a simple list, with optional null padding on the beginning and end. - * - * @param <Type> The type of the entries in the list. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public class NullPaddedList<Type> extends AbstractList<Type> { - List<Type> mList; - private int mTrailingNullCount; - private int mLeadingNullCount; - private int mPositionOffset; - - @Override - public String toString() { - return "NullPaddedList " + mLeadingNullCount - + ", " + mList.size() - + ", " + mTrailingNullCount; - } - - /** - * Create a static, immutable NullPaddedList with the specified list, - * - * @param leadingNullCount Number of empty items in advance of the passed list. - * @param list List of items. - * @param trailingNullCount Number of empty items following the passed list. - */ - NullPaddedList(int leadingNullCount, List<Type> list, int trailingNullCount) { - if (leadingNullCount < 0 || trailingNullCount < 0) { - throw new IllegalArgumentException("leading/trailing null count must be non-negative"); - } - if (list == null) { - throw new IllegalArgumentException("list must be non-null"); - } - mList = list; - mLeadingNullCount = leadingNullCount; - mTrailingNullCount = trailingNullCount; - } - - NullPaddedList(int leadingNullCount, int totalCount, List<Type> list) { - if (list == null) { - throw new IllegalArgumentException("list must be non-null"); - } - - int trailingNullCount = totalCount - (leadingNullCount) - list.size(); - - mList = list; - mLeadingNullCount = leadingNullCount; - mTrailingNullCount = trailingNullCount; - } - - NullPaddedList(int positionOffset, List<Type> list) { - if (list == null) { - throw new IllegalArgumentException("list must be non-null"); - } - - mList = list; - mPositionOffset = positionOffset; - } - - // --------------- PagedList API --------------- - - @Override - public Type get(int index) { - if (index < 0 || index >= size()) { - throw new IndexOutOfBoundsException(); - } - - index -= mLeadingNullCount; - if (index < 0) { - return null; - } - if (index >= mList.size()) { - return null; - } - return mList.get(index); - } - - @Override - public final int size() { - return getLoadedCount() + getLeadingNullCount() + getTrailingNullCount(); - } - - // --------------- Contiguous API --------------- - - public int getPositionOffset() { - return mPositionOffset; - } - - /** - * Number of loaded items. This does not account for leading or trailing null padding. - * - * @return Number of loaded items. - */ - public int getLoadedCount() { - return mList.size(); - } - - /** - * Number of empty, unloaded items ahead of the loaded item region. - * - * @return Number of nulls before the loaded list. - */ - public int getLeadingNullCount() { - return mLeadingNullCount; - } - - /** - * Number of empty, unloaded items behind the loaded item region. - * - * @return Number of nulls after the loaded list. - */ - public int getTrailingNullCount() { - return mTrailingNullCount; - } -} diff --git a/android/arch/paging/PageResult.java b/android/arch/paging/PageResult.java index a4090f61..55d5fb76 100644 --- a/android/arch/paging/PageResult.java +++ b/android/arch/paging/PageResult.java @@ -47,6 +47,14 @@ class PageResult<K, V> { this.positionOffset = positionOffset; } + PageResult(int type) { + this.type = type; + this.page = null; + this.leadingNulls = 0; + this.trailingNulls = 0; + this.positionOffset = 0; + } + interface Receiver<K, V> { @AnyThread void postOnPageResult(@NonNull PageResult<K, V> pageResult); diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java index 51f524af..f18e108c 100644 --- a/android/arch/paging/PagedList.java +++ b/android/arch/paging/PagedList.java @@ -16,8 +16,10 @@ package android.arch.paging; +import android.support.annotation.AnyThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.annotation.WorkerThread; import java.lang.ref.WeakReference; @@ -97,6 +99,8 @@ public abstract class PagedList<T> extends AbstractList<T> { final Executor mMainThreadExecutor; @NonNull final Executor mBackgroundThreadExecutor; + @Nullable + final BoundaryCallback<T> mBoundaryCallback; @NonNull final Config mConfig; @NonNull @@ -105,6 +109,16 @@ public abstract class PagedList<T> extends AbstractList<T> { int mLastLoad = 0; T mLastItem = null; + // if set to true, mBoundaryCallback is non-null, and should + // be dispatched when nearby load has occurred + private boolean mBoundaryCallbackBeginDeferred = false; + private boolean mBoundaryCallbackEndDeferred = false; + + // lowest and highest index accessed by loadAround. Used to + // decide when mBoundaryCallback should be dispatched + private int mLowestIndexAccessed = Integer.MAX_VALUE; + private int mHighestIndexAccessed = Integer.MIN_VALUE; + private final AtomicBoolean mDetached = new AtomicBoolean(false); protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); @@ -112,10 +126,12 @@ public abstract class PagedList<T> extends AbstractList<T> { PagedList(@NonNull PagedStorage<?, T> storage, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, + @Nullable BoundaryCallback<T> boundaryCallback, @NonNull Config config) { mStorage = storage; mMainThreadExecutor = mainThreadExecutor; mBackgroundThreadExecutor = backgroundThreadExecutor; + mBoundaryCallback = boundaryCallback; mConfig = config; } @@ -129,6 +145,7 @@ public abstract class PagedList<T> extends AbstractList<T> { * Generally, this is the UI/main thread. * @param backgroundThreadExecutor Data loading will be done via this executor - should be a * background thread. + * @param boundaryCallback Optional boundary callback to attach to the list. * @param config PagedList Config, which defines how the PagedList will load data. * @param <K> Key type that indicates to the DataSource what data to load. * @param <T> Type of items to be held and loaded by the PagedList. @@ -139,6 +156,7 @@ public abstract class PagedList<T> extends AbstractList<T> { private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, + @Nullable BoundaryCallback<T> boundaryCallback, @NonNull Config config, @Nullable K key) { if (dataSource.isContiguous() || !config.enablePlaceholders) { @@ -150,12 +168,14 @@ public abstract class PagedList<T> extends AbstractList<T> { return new ContiguousPagedList<>(contigDataSource, mainThreadExecutor, backgroundThreadExecutor, + boundaryCallback, config, key); } else { return new TiledPagedList<>((TiledDataSource<T>) dataSource, mainThreadExecutor, backgroundThreadExecutor, + boundaryCallback, config, (key != null) ? (Integer) key : 0); } @@ -186,6 +206,7 @@ public abstract class PagedList<T> extends AbstractList<T> { private DataSource<Key, Value> mDataSource; private Executor mMainThreadExecutor; private Executor mBackgroundThreadExecutor; + private BoundaryCallback mBoundaryCallback; private Config mConfig; private Key mInitialKey; @@ -229,6 +250,14 @@ public abstract class PagedList<T> extends AbstractList<T> { return this; } + @NonNull + public Builder<Key, Value> setBoundaryCallback( + @Nullable BoundaryCallback boundaryCallback) { + mBoundaryCallback = boundaryCallback; + return this; + } + + /** * The Config defining how the PagedList should load from the DataSource. * @@ -284,10 +313,12 @@ public abstract class PagedList<T> extends AbstractList<T> { throw new IllegalArgumentException("Config required"); } + //noinspection unchecked return PagedList.create( mDataSource, mMainThreadExecutor, mBackgroundThreadExecutor, + mBoundaryCallback, mConfig, mInitialKey); } @@ -312,7 +343,6 @@ public abstract class PagedList<T> extends AbstractList<T> { return item; } - /** * Load adjacent items to passed index. * @@ -321,8 +351,122 @@ public abstract class PagedList<T> extends AbstractList<T> { public void loadAround(int index) { mLastLoad = index + getPositionOffset(); loadAroundInternal(index); + + mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index); + mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index); + + /* + * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to + * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded, + * and accesses happen near the boundaries. + * + * Note: we post here, since RecyclerView may want to add items in response, and this + * call occurs in PagedListAdapter bind. + */ + tryDispatchBoundaryCallbacks(true); + } + + // Creation thread for initial synchronous load, otherwise main thread + // Safe to access main thread only state - no other thread has reference during construction + @AnyThread + void deferBoundaryCallbacks(final boolean deferEmpty, + final boolean deferBegin, final boolean deferEnd) { + if (mBoundaryCallback == null) { + throw new IllegalStateException("Computing boundary"); + } + + /* + * If lowest/highest haven't been initialized, set them to storage size, + * since placeholders must already be computed by this point. + * + * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately + * if the initial load size is smaller than the prefetch window (see + * TiledPagedListTest#boundaryCallback_immediate()) + */ + if (mLowestIndexAccessed == Integer.MAX_VALUE) { + mLowestIndexAccessed = mStorage.size(); + } + if (mHighestIndexAccessed == Integer.MIN_VALUE) { + mHighestIndexAccessed = 0; + } + + if (deferEmpty || deferBegin || deferEnd) { + // Post to the main thread, since we may be on creation thread currently + mMainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + // on is dispatched immediately, since items won't be accessed + //noinspection ConstantConditions + if (deferEmpty) { + mBoundaryCallback.onZeroItemsLoaded(); + } + + // for other callbacks, mark deferred, and only dispatch if loadAround + // has been called near to the position + if (deferBegin) { + mBoundaryCallbackBeginDeferred = true; + } + if (deferEnd) { + mBoundaryCallbackEndDeferred = true; + } + tryDispatchBoundaryCallbacks(false); + } + }); + } } + /** + * Call this when mLowest/HighestIndexAccessed are changed, or + * mBoundaryCallbackBegin/EndDeferred is set. + */ + private void tryDispatchBoundaryCallbacks(boolean post) { + final boolean dispatchBegin = mBoundaryCallbackBeginDeferred + && mLowestIndexAccessed <= mConfig.prefetchDistance; + final boolean dispatchEnd = mBoundaryCallbackEndDeferred + && mHighestIndexAccessed >= size() - mConfig.prefetchDistance; + + if (!dispatchBegin && !dispatchEnd) { + return; + } + + if (dispatchBegin) { + mBoundaryCallbackBeginDeferred = false; + } + if (dispatchEnd) { + mBoundaryCallbackEndDeferred = false; + } + if (post) { + mMainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd); + } + }); + } else { + dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd); + } + } + + private void dispatchBoundaryCallbacks(boolean begin, boolean end) { + // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present + if (begin) { + //noinspection ConstantConditions + mBoundaryCallback.onItemAtFrontLoaded( + snapshot(), mStorage.getFirstLoadedItem(), mStorage.size()); + } + if (end) { + //noinspection ConstantConditions + mBoundaryCallback.onItemAtEndLoaded( + snapshot(), mStorage.getLastLoadedItem(), mStorage.size()); + } + } + + /** @hide */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + void offsetBoundaryAccessIndices(int offset) { + mLowestIndexAccessed += offset; + mHighestIndexAccessed += offset; + } /** * Returns size of the list, including any not-yet-loaded null padding. @@ -351,6 +495,7 @@ public abstract class PagedList<T> extends AbstractList<T> { * * @return Immutable snapshot of PagedList data. */ + @SuppressWarnings("WeakerAccess") @NonNull public List<T> snapshot() { if (isImmutable()) { @@ -726,4 +871,15 @@ public abstract class PagedList<T> extends AbstractList<T> { } } } + + /** + * WIP API for load-more-into-local-storage callbacks + */ + public abstract static class BoundaryCallback<T> { + public abstract void onZeroItemsLoaded(); + public abstract void onItemAtFrontLoaded(@NonNull List<T> pagedListSnapshot, + @NonNull T itemAtFront, int pagedListSize); + public abstract void onItemAtEndLoaded(@NonNull List<T> pagedListSnapshot, + @NonNull T itemAtEnd, int pagedListSize); + } } diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java index 93c02ea3..89b9c2ee 100644 --- a/android/arch/paging/PagedListAdapter.java +++ b/android/arch/paging/PagedListAdapter.java @@ -113,6 +113,13 @@ import android.support.v7.widget.RecyclerView; public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> { private final PagedListAdapterHelper<T> mHelper; + private final PagedListAdapterHelper.PagedListListener<T> mListener = + new PagedListAdapterHelper.PagedListListener<T>() { + @Override + public void onCurrentListChanged(@Nullable PagedList<T> currentList) { + PagedListAdapter.this.onCurrentListChanged(currentList); + } + }; /** * Creates a PagedListAdapter with default threading and @@ -125,11 +132,13 @@ public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder> */ protected PagedListAdapter(@NonNull DiffCallback<T> diffCallback) { mHelper = new PagedListAdapterHelper<>(this, diffCallback); + mHelper.mListener = mListener; } @SuppressWarnings("unused, WeakerAccess") protected PagedListAdapter(@NonNull ListAdapterConfig<T> config) { mHelper = new PagedListAdapterHelper<>(new ListAdapterHelper.AdapterCallback(this), config); + mHelper.mListener = mListener; } /** @@ -167,4 +176,22 @@ public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder> public PagedList<T> getCurrentList() { return mHelper.getCurrentList(); } + + /** + * Called when the current PagedList is updated. + * <p> + * This may be dispatched as part of {@link #setList(PagedList)} if a background diff isn't + * needed (such as when the first list is passed, or the list is cleared). In either case, + * PagedListAdapter will simply call + * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}. + * <p> + * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList + * to a snapshot version of the PagedList during a diff. This means you cannot observe each + * PagedList via this method. + * + * @param currentList new PagedList being displayed, may be null. + */ + @SuppressWarnings("WeakerAccess") + public void onCurrentListChanged(@Nullable PagedList<T> currentList) { + } } diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java index abcff415..51a6e37f 100644 --- a/android/arch/paging/PagedListAdapterHelper.java +++ b/android/arch/paging/PagedListAdapterHelper.java @@ -123,6 +123,14 @@ public class PagedListAdapterHelper<T> { private final ListUpdateCallback mUpdateCallback; private final ListAdapterConfig<T> mConfig; + // TODO: REAL API + interface PagedListListener<T> { + void onCurrentListChanged(@Nullable PagedList<T> currentList); + } + + @Nullable + PagedListListener<T> mListener; + private boolean mIsContiguous; private PagedList<T> mPagedList; @@ -247,6 +255,9 @@ public class PagedListAdapterHelper<T> { } // dispatch update callback after updating mPagedList/mSnapshot mUpdateCallback.onRemoved(0, removedCount); + if (mListener != null) { + mListener.onCurrentListChanged(null); + } return; } @@ -257,6 +268,10 @@ public class PagedListAdapterHelper<T> { // dispatch update callback after updating mPagedList/mSnapshot mUpdateCallback.onInserted(0, pagedList.size()); + + if (mListener != null) { + mListener.onCurrentListChanged(pagedList); + } return; } @@ -311,6 +326,9 @@ public class PagedListAdapterHelper<T> { previousSnapshot.mStorage, newList.mStorage, diffResult); newList.addWeakCallback(diffSnapshot, mPagedListCallback); + if (mListener != null) { + mListener.onCurrentListChanged(mPagedList); + } } /** diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java index 7f91290d..b857462b 100644 --- a/android/arch/paging/PagedStorage.java +++ b/android/arch/paging/PagedStorage.java @@ -230,13 +230,13 @@ final class PagedStorage<K, V> extends AbstractList<V> { // ---------------- Contiguous API ------------------- - V getFirstContiguousItem() { + V getFirstLoadedItem() { // safe to access first page's first item here: // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty return mPages.get(0).items.get(0); } - V getLastContiguousItem() { + V getLastLoadedItem() { // safe to access last page's last item here: // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty Page<K, V> page = mPages.get(mPages.size() - 1); diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java index c538cb60..fa2932ad 100644 --- a/android/arch/paging/PositionalDataSource.java +++ b/android/arch/paging/PositionalDataSource.java @@ -18,7 +18,6 @@ package android.arch.paging; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.RestrictTo; import android.support.annotation.WorkerThread; import java.util.List; @@ -38,10 +37,8 @@ import java.util.List; * backend or data store doesn't require * <p> * @param <Value> Value type of items being loaded by the DataSource. - * @hide */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> { +abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> { /** * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED. @@ -66,13 +63,10 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I return loadBefore(currentBeginIndex - 1, pageSize); } - /** @hide */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - @WorkerThread - @Nullable @Override - public NullPaddedList<Value> loadInitial( - Integer position, int initialLoadSize, boolean enablePlaceholders) { + void loadInitial(Integer position, int initialLoadSize, boolean enablePlaceholders, + @NonNull PageResult.Receiver<Integer, Value> receiver) { + final int convertPosition = position == null ? 0 : position; final int loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2)); @@ -81,11 +75,23 @@ public abstract class PositionalDataSource<Value> extends ContiguousDataSource<I count = countItems(); } List<Value> data = loadAfter(loadPosition, initialLoadSize); - if (count == COUNT_UNDEFINED) { - return new NullPaddedList<>(loadPosition, data); - } else { - return new NullPaddedList<>(loadPosition, count, data); + + if (data == null) { + receiver.onPageResult(new PageResult<Integer, Value>(PageResult.INIT)); + return; } + + final boolean uncounted = count == COUNT_UNDEFINED; + int leadingNullCount = uncounted ? 0 : loadPosition; + int trailingNullCount = uncounted ? 0 : count - leadingNullCount - data.size(); + int positionOffset = uncounted ? loadPosition : 0; + + receiver.onPageResult(new PageResult<>( + PageResult.INIT, + new Page<Integer, Value>(data), + leadingNullCount, + trailingNullCount, + positionOffset)); } /** diff --git a/android/arch/paging/SnapshotPagedList.java b/android/arch/paging/SnapshotPagedList.java index 7e965a0f..6a8a748c 100644 --- a/android/arch/paging/SnapshotPagedList.java +++ b/android/arch/paging/SnapshotPagedList.java @@ -27,6 +27,7 @@ class SnapshotPagedList<T> extends PagedList<T> { super(pagedList.mStorage.snapshot(), pagedList.mMainThreadExecutor, pagedList.mBackgroundThreadExecutor, + null, pagedList.mConfig); mContiguous = pagedList.isContiguous(); mLastKey = pagedList.getLastKey(); diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java index 61dead3a..0ea94286 100644 --- a/android/arch/paging/TiledDataSource.java +++ b/android/arch/paging/TiledDataSource.java @@ -87,6 +87,8 @@ import java.util.List; */ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> { + private int mItemCount; + /** * Number of items that this DataSource can provide in total. * @@ -123,6 +125,7 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> { */ void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount, PageResult.Receiver<Integer, Type> receiver) { + mItemCount = itemCount; if (itemCount == 0) { // no data to load, just immediately return empty @@ -132,7 +135,6 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> { return; } - List<Type> list = loadRangeWrapper(startPosition, count); count = Math.min(count, itemCount - startPosition); @@ -167,9 +169,15 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> { void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) { List<Type> list = loadRangeWrapper(startPosition, count); - Page<Integer, Type> page = list != null ? new Page<Integer, Type>(list) : null; + Page<Integer, Type> page = null; + int trailingNulls = mItemCount - startPosition; + + if (list != null) { + page = new Page<Integer, Type>(list); + trailingNulls -= list.size(); + } receiver.postOnPageResult(new PageResult<>( - PageResult.TILE, page, startPosition, 0, 0)); + PageResult.TILE, page, startPosition, trailingNulls, 0)); } private List<Type> loadRangeWrapper(int startPosition, int count) { diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java index 934a0dd0..76bb682d 100644 --- a/android/arch/paging/TiledPagedList.java +++ b/android/arch/paging/TiledPagedList.java @@ -17,7 +17,6 @@ package android.arch.paging; import android.support.annotation.AnyThread; -import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; @@ -46,7 +45,9 @@ class TiledPagedList<T> extends PagedList<T> }); } - @MainThread + // Creation thread for initial synchronous load, otherwise main thread + // Safe to access main thread only state - no other thread has reference during construction + @AnyThread @Override public void onPageResult(@NonNull PageResult<Integer, T> pageResult) { if (pageResult.page == null) { @@ -67,6 +68,13 @@ class TiledPagedList<T> extends PagedList<T> mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page, TiledPagedList.this); } + + if (mBoundaryCallback != null) { + boolean deferEmpty = mStorage.size() == 0; + boolean deferBegin = !deferEmpty && pageResult.leadingNulls == 0; + boolean deferEnd = !deferEmpty && pageResult.trailingNulls == 0; + deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd); + } } }; @@ -74,15 +82,17 @@ class TiledPagedList<T> extends PagedList<T> TiledPagedList(@NonNull TiledDataSource<T> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, + @Nullable BoundaryCallback<T> boundaryCallback, @NonNull Config config, int position) { - super(new PagedStorage<Integer, T>(), - mainThreadExecutor, backgroundThreadExecutor, config); + super(new PagedStorage<Integer, T>(), mainThreadExecutor, backgroundThreadExecutor, + boundaryCallback, config); mDataSource = dataSource; final int pageSize = mConfig.pageSize; final int itemCount = mDataSource.countItems(); + final int firstLoadSize = Math.min(itemCount, (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize); final int firstLoadPosition = computeFirstLoadPosition( diff --git a/android/arch/paging/integration/testapp/PagedListItemAdapter.java b/android/arch/paging/integration/testapp/PagedListItemAdapter.java index 3522e437..d1ae5aba 100644 --- a/android/arch/paging/integration/testapp/PagedListItemAdapter.java +++ b/android/arch/paging/integration/testapp/PagedListItemAdapter.java @@ -23,7 +23,7 @@ import android.view.ViewGroup; import android.widget.TextView; /** - * Sample NullPaddedList adapter, which uses a PagedListAdapterHelper. + * Sample PagedList item Adapter, which uses a PagedListAdapterHelper. */ class PagedListItemAdapter extends PagedListAdapter<Item, RecyclerView.ViewHolder> { diff --git a/android/arch/paging/integration/testapp/PagedListItemViewModel.java b/android/arch/paging/integration/testapp/PagedListItemViewModel.java index 237cc140..974eab95 100644 --- a/android/arch/paging/integration/testapp/PagedListItemViewModel.java +++ b/android/arch/paging/integration/testapp/PagedListItemViewModel.java @@ -19,7 +19,7 @@ package android.arch.paging.integration.testapp; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.ViewModel; import android.arch.paging.DataSource; -import android.arch.paging.LivePagedListProvider; +import android.arch.paging.LivePagedListBuilder; import android.arch.paging.PagedList; /** @@ -41,16 +41,19 @@ public class PagedListItemViewModel extends ViewModel { LiveData<PagedList<Item>> getLivePagedList() { if (mLivePagedList == null) { - mLivePagedList = new LivePagedListProvider<Integer, Item>() { - @Override - protected DataSource<Integer, Item> createDataSource() { - ItemDataSource newDataSource = new ItemDataSource(); - synchronized (mDataSourceLock) { - mDataSource = newDataSource; - return mDataSource; - } - } - }.create(0, 20); + mLivePagedList = new LivePagedListBuilder<Integer, Item>() + .setPagingConfig(20) + .setDataSourceFactory(new DataSource.Factory<Integer, Item>() { + @Override + public DataSource<Integer, Item> create() { + ItemDataSource newDataSource = new ItemDataSource(); + synchronized (mDataSourceLock) { + mDataSource = newDataSource; + return mDataSource; + } + } + }) + .build(); } return mLivePagedList; diff --git a/android/arch/paging/integration/testapp/PagedListSampleActivity.java b/android/arch/paging/integration/testapp/PagedListSampleActivity.java index 5d0117d7..f1f233f5 100644 --- a/android/arch/paging/integration/testapp/PagedListSampleActivity.java +++ b/android/arch/paging/integration/testapp/PagedListSampleActivity.java @@ -16,7 +16,6 @@ package android.arch.paging.integration.testapp; -import android.arch.lifecycle.LifecycleRegistry; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; import android.arch.paging.PagedList; @@ -28,7 +27,7 @@ import android.view.View; import android.widget.Button; /** - * Sample NullPaddedList activity with artificial data source. + * Sample PagedList activity with artificial data source. */ public class PagedListSampleActivity extends AppCompatActivity { @@ -56,11 +55,4 @@ public class PagedListSampleActivity extends AppCompatActivity { } }); } - - private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); - - @Override - public LifecycleRegistry getLifecycle() { - return mLifecycleRegistry; - } } diff --git a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java index 320b2cdd..89d16b7e 100644 --- a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java +++ b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java @@ -21,7 +21,7 @@ import android.arch.core.executor.ArchTaskExecutor; import android.arch.lifecycle.AndroidViewModel; import android.arch.lifecycle.LiveData; import android.arch.paging.DataSource; -import android.arch.paging.LivePagedListProvider; +import android.arch.paging.LivePagedListBuilder; import android.arch.paging.PagedList; import android.arch.persistence.room.Room; import android.arch.persistence.room.integration.testapp.database.Customer; @@ -81,30 +81,30 @@ public class CustomerViewModel extends AndroidViewModel { }); } + private static <K> LiveData<PagedList<Customer>> getLivePagedList( + K initialLoadKey, DataSource.Factory<K, Customer> dataSourceFactory) { + return new LivePagedListBuilder<K, Customer>() + .setInitialLoadKey(initialLoadKey) + .setPagingConfig(new PagedList.Config.Builder() + .setPageSize(10) + .setEnablePlaceholders(false) + .build()) + .setDataSourceFactory(dataSourceFactory) + .build(); + } + LiveData<PagedList<Customer>> getLivePagedList(int position) { if (mLiveCustomerList == null) { - mLiveCustomerList = mDatabase.getCustomerDao() - .loadPagedAgeOrder().create(position, - new PagedList.Config.Builder() - .setPageSize(10) - .setEnablePlaceholders(false) - .build()); + mLiveCustomerList = + getLivePagedList(position, mDatabase.getCustomerDao().loadPagedAgeOrder()); } return mLiveCustomerList; } LiveData<PagedList<Customer>> getLivePagedList(String key) { if (mLiveCustomerList == null) { - mLiveCustomerList = new LivePagedListProvider<String, Customer>() { - @Override - protected DataSource<String, Customer> createDataSource() { - return new LastNameAscCustomerDataSource(mDatabase); - } - }.create(key, - new PagedList.Config.Builder() - .setPageSize(10) - .setEnablePlaceholders(false) - .build()); + mLiveCustomerList = + getLivePagedList(key, LastNameAscCustomerDataSource.factory(mDatabase)); } return mLiveCustomerList; } diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java index 665a1aeb..cb2bb034 100644 --- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java +++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java @@ -17,6 +17,7 @@ package android.arch.persistence.room.integration.testapp.dao; import android.arch.lifecycle.LiveData; +import android.arch.paging.DataSource; import android.arch.paging.LivePagedListProvider; import android.arch.paging.TiledDataSource; import android.arch.persistence.room.Dao; @@ -184,7 +185,10 @@ public abstract class UserDao { } @Query("SELECT * FROM user where mAge > :age") - public abstract LivePagedListProvider<Integer, User> loadPagedByAge(int age); + public abstract DataSource.Factory<Integer, User> loadPagedByAge(int age); + + @Query("SELECT * FROM user where mAge > :age") + public abstract LivePagedListProvider<Integer, User> loadPagedByAge_legacy(int age); @Query("SELECT * FROM user ORDER BY mAge DESC") public abstract TiledDataSource<User> loadUsersByAgeDesc(); @@ -200,67 +204,6 @@ public abstract class UserDao { @Query("SELECT COUNT(*) from user") public abstract Integer getUserCount(); - - // QueryDataSourceTest - name desc - - // limit-offset - @Query("SELECT * from user ORDER BY mName DESC LIMIT :limit OFFSET :offset") - public abstract List<User> userNameLimitOffset(int limit, int offset); - - // keyed - @Query("SELECT * from user ORDER BY mName DESC LIMIT :limit") - public abstract List<User> userNameInitial(int limit); - - @Query("SELECT * from user WHERE mName < :key ORDER BY mName DESC LIMIT :limit") - public abstract List<User> userNameLoadAfter(String key, int limit); - - @Query("SELECT COUNT(*) from user WHERE mName < :key ORDER BY mName DESC") - public abstract int userNameCountAfter(String key); - - @Query("SELECT * from user WHERE mName > :key ORDER BY mName ASC LIMIT :limit") - public abstract List<User> userNameLoadBefore(String key, int limit); - - @Query("SELECT COUNT(*) from user WHERE mName > :key ORDER BY mName ASC") - public abstract int userNameCountBefore(String key); - - - - // ComplexQueryDataSourceTest - last desc, first asc, id desc - - // limit-offset - @Query("SELECT * from user" - + " ORDER BY mLastName DESC, mName ASC, mId DESC" - + " LIMIT :limit OFFSET :offset") - public abstract List<User> userComplexLimitOffset(int limit, int offset); - - // keyed - @Query("SELECT * from user" - + " ORDER BY mLastName DESC, mName ASC, mId DESC" - + " LIMIT :limit") - public abstract List<User> userComplexInitial(int limit); - - @Query("SELECT * from user" - + " WHERE mLastName < :lastName or (mLastName = :lastName and (mName > :name or (mName = :name and mId < :id)))" - + " ORDER BY mLastName DESC, mName ASC, mId DESC" - + " LIMIT :limit") - public abstract List<User> userComplexLoadAfter(String lastName, String name, int id, int limit); - - @Query("SELECT COUNT(*) from user" - + " WHERE mLastName < :lastName or (mLastName = :lastName and (mName > :name or (mName = :name and mId < :id)))" - + " ORDER BY mLastName DESC, mName ASC, mId DESC") - public abstract int userComplexCountAfter(String lastName, String name, int id); - - @Query("SELECT * from user" - + " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))" - + " ORDER BY mLastName ASC, mName DESC, mId ASC" - + " LIMIT :limit") - public abstract List<User> userComplexLoadBefore(String lastName, String name, int id, int limit); - - @Query("SELECT COUNT(*) from user" - + " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))" - + " ORDER BY mLastName ASC, mName DESC, mId ASC") - public abstract int userComplexCountBefore(String lastName, String name, int id); - @Transaction public void insertBothByAnnotation(final User a, final User b) { insert(a); diff --git a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java index b5df914a..db45dc46 100644 --- a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java +++ b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java @@ -16,7 +16,7 @@ package android.arch.persistence.room.integration.testapp.database; -import android.arch.paging.LivePagedListProvider; +import android.arch.paging.DataSource; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Insert; import android.arch.persistence.room.Query; @@ -44,12 +44,11 @@ public interface CustomerDao { void insertAll(Customer[] customers); /** - * @return LivePagedListProvider of customers, ordered by last name. Call - * {@link LivePagedListProvider#create(Object, android.arch.paging.PagedList.Config)} to - * get a LiveData of PagedLists. + * @return DataSource.Factory of customers, ordered by last name. Use + * {@link android.arch.paging.LivePagedListBuilder} to get a LiveData of PagedLists. */ @Query("SELECT * FROM customer ORDER BY mLastName ASC") - LivePagedListProvider<Integer, Customer> loadPagedAgeOrder(); + DataSource.Factory<Integer, Customer> loadPagedAgeOrder(); /** * @return number of customers diff --git a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java index 1bc731a5..a38d6aed 100644 --- a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java +++ b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java @@ -15,6 +15,7 @@ */ package android.arch.persistence.room.integration.testapp.database; +import android.arch.paging.DataSource; import android.arch.paging.KeyedDataSource; import android.arch.persistence.room.InvalidationTracker; import android.support.annotation.NonNull; @@ -32,10 +33,19 @@ public class LastNameAscCustomerDataSource extends KeyedDataSource<String, Custo private final InvalidationTracker.Observer mObserver; private SampleDatabase mDb; + public static Factory<String, Customer> factory(final SampleDatabase db) { + return new Factory<String, Customer>() { + @Override + public DataSource<String, Customer> create() { + return new LastNameAscCustomerDataSource(db); + } + }; + } + /** * Create a DataSource from the customer table of the given database */ - public LastNameAscCustomerDataSource(SampleDatabase db) { + private LastNameAscCustomerDataSource(SampleDatabase db) { mDb = db; mCustomerDao = db.getCustomerDao(); mObserver = new InvalidationTracker.Observer("customer") { diff --git a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java index df70a170..c5465314 100644 --- a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java +++ b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java @@ -28,6 +28,7 @@ import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.LifecycleRegistry; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; +import android.arch.paging.LivePagedListBuilder; import android.arch.paging.PagedList; import android.arch.persistence.room.integration.testapp.test.TestDatabaseTest; import android.arch.persistence.room.integration.testapp.test.TestUtil; @@ -46,15 +47,54 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +@LargeTest @RunWith(AndroidJUnit4.class) -public class LivePagedListProviderTest extends TestDatabaseTest { +public class DataSourceFactoryTest extends TestDatabaseTest { @Rule public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule(); + private interface LivePagedListFactory { + LiveData<PagedList<User>> create(); + } + @Test - @LargeTest public void getUsersAsPagedList() throws InterruptedException, ExecutionException, TimeoutException { + validateUsersAsPagedList(new LivePagedListFactory() { + @Override + public LiveData<PagedList<User>> create() { + return new LivePagedListBuilder<Integer, User>() + .setPagingConfig(new PagedList.Config.Builder() + .setPageSize(10) + .setPrefetchDistance(1) + .setInitialLoadSizeHint(10).build()) + .setDataSourceFactory(mUserDao.loadPagedByAge(3)) + .build(); + } + }); + } + + + // TODO: delete this and factory abstraction when LivePagedListProvider is removed + @Test + public void getUsersAsPagedList_legacyLivePagedListProvider() + throws InterruptedException, ExecutionException, TimeoutException { + validateUsersAsPagedList(new LivePagedListFactory() { + @Override + public LiveData<PagedList<User>> create() { + return mUserDao.loadPagedByAge_legacy(3).create( + 0, + new PagedList.Config.Builder() + .setPageSize(10) + .setPrefetchDistance(1) + .setInitialLoadSizeHint(10) + .build()); + } + }); + } + + private void validateUsersAsPagedList(LivePagedListFactory factory) + throws InterruptedException, ExecutionException, TimeoutException { mDatabase.beginTransaction(); try { for (int i = 0; i < 100; i++) { @@ -67,12 +107,8 @@ public class LivePagedListProviderTest extends TestDatabaseTest { mDatabase.endTransaction(); } assertThat(mUserDao.count(), is(100)); - final LiveData<PagedList<User>> livePagedUsers = mUserDao.loadPagedByAge(3).create( - 0, - new PagedList.Config.Builder() - .setPageSize(10) - .setPrefetchDistance(1) - .setInitialLoadSizeHint(10).build()); + + final LiveData<PagedList<User>> livePagedUsers = factory.create(); final TestLifecycleOwner testOwner = new TestLifecycleOwner(); testOwner.handleEvent(Lifecycle.Event.ON_CREATE); diff --git a/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java deleted file mode 100644 index 4d58512a..00000000 --- a/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java +++ /dev/null @@ -1,185 +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 android.arch.persistence.room.integration.testapp.test; - -import android.arch.paging.BoundedDataSource; -import android.arch.paging.KeyedDataSource; -import android.arch.persistence.room.integration.testapp.vo.User; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -@MediumTest -@RunWith(AndroidJUnit4.class) -public class ComplexQueryDataSourceTest extends TestDatabaseTest { - - @SuppressWarnings("WeakerAccess") - public class LastFirstIdKey { - public final String lastName; - public final String name; - public final int id; - - public LastFirstIdKey(String lastName, String name, int id) { - this.lastName = lastName; - this.name = name; - this.id = id; - } - } - - /** - * Proper, keyed implementation. - */ - public class KeyedUserQueryDataSource extends KeyedDataSource<LastFirstIdKey, User> { - - @NonNull - @Override - public LastFirstIdKey getKey(@NonNull User user) { - return new LastFirstIdKey( - user.getLastName(), - user.getName(), - user.getId()); - } - - @Override - public int countItemsBefore(@NonNull LastFirstIdKey key) { - return mUserDao.userComplexCountBefore( - key.lastName, - key.name, - key.id); - } - - @Override - public int countItemsAfter(@NonNull LastFirstIdKey key) { - return mUserDao.userComplexCountAfter( - key.lastName, - key.name, - key.id); - } - - @Nullable - @Override - public List<User> loadInitial(int pageSize) { - return mUserDao.userComplexInitial(pageSize); - } - - @Nullable - @Override - public List<User> loadBefore(@NonNull LastFirstIdKey key, int pageSize) { - return mUserDao.userComplexLoadBefore( - key.lastName, - key.name, - key.id, - pageSize); - } - - @Nullable - @Override - public List<User> loadAfter(@Nullable LastFirstIdKey key, int pageSize) { - return mUserDao.userComplexLoadAfter( - key.lastName, - key.name, - key.id, - pageSize); - } - } - - /** - * Lazy, LIMIT/OFFSET implementation. - */ - public class OffsetUserQueryDataSource extends BoundedDataSource<User> { - - @Override - public int countItems() { - return mUserDao.getUserCount(); - } - - @Nullable - @Override - public List<User> loadRange(int startPosition, int loadCount) { - return mUserDao.userComplexLimitOffset(loadCount, startPosition); - } - } - - private static final User[] USERS_BY_LAST_FIRST_ID = new User[100]; - - @BeforeClass - public static void setupClass() { - String[] lastNames = new String[10]; - - String[] firstNames = new String[10]; - for (int i = 0; i < 10; i++) { - lastNames[i] = "f" + (char) ('a' + i); - firstNames[i] = "l" + (char) ('a' + i); - } - - for (int i = 0; i < USERS_BY_LAST_FIRST_ID.length; i++) { - User user = new User(); - user.setId(i); - user.setName(firstNames[i % 10]); - user.setLastName(lastNames[(i / 10) % 10]); - user.setAge((int) (10 + Math.random() * 50)); - user.setCustomField(UUID.randomUUID().toString()); - user.setBirthday(new Date()); - USERS_BY_LAST_FIRST_ID[i] = user; - } - } - - @Before - public void setup() { - mUserDao.insertAll(USERS_BY_LAST_FIRST_ID); - - Arrays.sort(USERS_BY_LAST_FIRST_ID, new Comparator<User>() { - @Override - public int compare(User o1, User o2) { - int diff = o2.getLastName().compareTo(o1.getLastName()); - if (diff != 0) { - return diff; - } - diff = o2.getName().compareTo(o1.getName()); - if (diff != 0) { - return -diff; // Note: 'mName' is ASC, therefore diff reversed - } - - return o2.getId() - o1.getId(); - } - }); - } - - @Test - public void testKeyedQueryDataSource() { - QueryDataSourceTest.verifyUserDataSource(USERS_BY_LAST_FIRST_ID, - new KeyedUserQueryDataSource()); - } - - @Test - public void testIndexedQueryDataSourceFull() { - QueryDataSourceTest.verifyUserDataSource(USERS_BY_LAST_FIRST_ID, - new OffsetUserQueryDataSource()); - } -} diff --git a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java deleted file mode 100644 index 2735c05a..00000000 --- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java +++ /dev/null @@ -1,179 +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 android.arch.persistence.room.integration.testapp.test; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import android.arch.paging.BoundedDataSource; -import android.arch.paging.ContiguousDataSource; -import android.arch.paging.KeyedDataSource; -import android.arch.paging.NullPaddedList; -import android.arch.paging.PositionalDataSource; -import android.arch.persistence.room.integration.testapp.vo.User; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.test.filters.MediumTest; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -@MediumTest -@RunWith(AndroidJUnit4.class) -public class QueryDataSourceTest extends TestDatabaseTest { - /** - * Proper, keyed implementation. - */ - public class KeyedUserQueryDataSource extends KeyedDataSource<String, User> { - @NonNull - @Override - public String getKey(@NonNull User item) { - return item.getName(); - } - - @Override - public int countItemsBefore(@NonNull String userName) { - return mUserDao.userNameCountBefore(userName); - } - - @Override - public int countItemsAfter(@NonNull String userName) { - return mUserDao.userNameCountAfter(userName); - } - - @Nullable - @Override - public List<User> loadInitial(int pageSize) { - return mUserDao.userNameInitial(pageSize); - } - - @Nullable - @Override - public List<User> loadBefore(@NonNull String userName, int pageSize) { - return mUserDao.userNameLoadBefore(userName, pageSize); - } - - @Nullable - @Override - public List<User> loadAfter(@Nullable String userName, int pageSize) { - return mUserDao.userNameLoadAfter(userName, pageSize); - } - } - - /** - * Lazy, LIMIT/OFFSET implementation. - */ - public class OffsetUserQueryDataSource extends BoundedDataSource<User> { - @Override - public int countItems() { - return mUserDao.getUserCount(); - } - - @Nullable - @Override - public List<User> loadRange(int startPosition, int loadCount) { - return mUserDao.userNameLimitOffset(loadCount, startPosition); - } - } - - private static final User[] USERS_BY_NAME = new User[50]; - - @BeforeClass - public static void setupClass() { - for (int i = 0; i < USERS_BY_NAME.length; i++) { - USERS_BY_NAME[i] = TestUtil.createUser(i); - } - } - - @Before - public void setup() { - mUserDao.insertAll(USERS_BY_NAME); - - Arrays.sort(USERS_BY_NAME, new Comparator<User>() { - @Override - public int compare(User o1, User o2) { - return o2.getName().compareTo(o1.getName()); - } - }); - } - - @Test - public void testKeyedQueryDataSource() { - verifyUserDataSource(USERS_BY_NAME, new KeyedUserQueryDataSource()); - } - - @Test - public void testIndexedQueryDataSourceFull() { - verifyUserDataSource(USERS_BY_NAME, new OffsetUserQueryDataSource()); - } - - - public static <Key> void verifyUserDataSource(User[] expected, - ContiguousDataSource<Key, User> dataSource) { - List<User> list = new ArrayList<>(); - - Object key; - if (dataSource instanceof PositionalDataSource) { - // start at 15 by loading 10 items around key 20 - key = 20; - } else { - // start at 15 by loading 10 items around key 19 (note, keyed is exclusive, pos isn't) - KeyedDataSource<String, User> keyedDataSource = - (KeyedDataSource<String, User>) dataSource; - key = keyedDataSource.getKey(expected[19]); - } - @SuppressWarnings("unchecked") - NullPaddedList<User> initial = dataSource.loadInitial((Key) key, 10, true); - - assertNotNull(initial); - assertEquals(15, initial.getLeadingNullCount()); - assertEquals(expected.length - 25, initial.getTrailingNullCount()); - assertEquals(expected.length, initial.size()); - - for (int i = 15; i < initial.size() - initial.getTrailingNullCount(); i++) { - list.add(initial.get(i)); - } - - assertArrayEquals(Arrays.copyOfRange(expected, 15, 25), list.toArray()); - List<User> p = dataSource.loadAfter(24, list.get(list.size() - 1), 10); - assertNotNull(p); - list.addAll(p); - - assertArrayEquals(Arrays.copyOfRange(expected, 15, 35), list.toArray()); - - p = dataSource.loadBefore(15, list.get(0), 10); - assertNotNull(p); - list.addAll(0, p); - - assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray()); - - p = dataSource.loadBefore(5, list.get(0), 10); - assertNotNull(p); - list.addAll(0, p); - - assertArrayEquals(Arrays.copyOfRange(expected, 0, 35), list.toArray()); - } -} diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java index 854c8627..291cfd67 100644 --- a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java +++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java @@ -25,7 +25,8 @@ import android.arch.core.executor.testing.CountingTaskExecutorRule; import android.arch.lifecycle.Lifecycle; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; -import android.arch.paging.LivePagedListProvider; +import android.arch.paging.DataSource; +import android.arch.paging.LivePagedListBuilder; import android.arch.paging.PagedList; import android.arch.paging.TiledDataSource; import android.arch.persistence.room.Dao; @@ -202,7 +203,10 @@ public class QueryTransactionTest { @Test public void pagedList() { - LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10); + LiveData<PagedList<Entity1>> pagedList = new LivePagedListBuilder<Integer, Entity1>() + .setDataSourceFactory(mDao.pagedList()) + .setPagingConfig(10) + .build(); observeForever(pagedList); drain(); assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0)); @@ -366,7 +370,7 @@ public class QueryTransactionTest { List<Entity1WithChildren> withRelation(); - LivePagedListProvider<Integer, Entity1> pagedList(); + DataSource.Factory<Integer, Entity1> pagedList(); TiledDataSource<Entity1> dataSource(); @@ -406,7 +410,7 @@ public class QueryTransactionTest { @Override @Query(SELECT_ALL) - LivePagedListProvider<Integer, Entity1> pagedList(); + DataSource.Factory<Integer, Entity1> pagedList(); @Override @Query(SELECT_ALL) @@ -448,7 +452,7 @@ public class QueryTransactionTest { @Override @Transaction @Query(SELECT_ALL) - LivePagedListProvider<Integer, Entity1> pagedList(); + DataSource.Factory<Integer, Entity1> pagedList(); @Override @Transaction diff --git a/android/bluetooth/BluetoothGattCharacteristic.java b/android/bluetooth/BluetoothGattCharacteristic.java index 2c124034..243ad359 100644 --- a/android/bluetooth/BluetoothGattCharacteristic.java +++ b/android/bluetooth/BluetoothGattCharacteristic.java @@ -120,7 +120,7 @@ public class BluetoothGattCharacteristic implements Parcelable { public static final int WRITE_TYPE_DEFAULT = 0x02; /** - * Wrtite characteristic without requiring a response by the remote device + * Write characteristic without requiring a response by the remote device */ public static final int WRITE_TYPE_NO_RESPONSE = 0x01; diff --git a/android/bluetooth/BluetoothHidDevice.java b/android/bluetooth/BluetoothHidDevice.java index 179f36da..e3d763ab 100644 --- a/android/bluetooth/BluetoothHidDevice.java +++ b/android/bluetooth/BluetoothHidDevice.java @@ -31,7 +31,14 @@ import java.util.Arrays; import java.util.List; /** - * @hide + * Provides the public APIs to control the Bluetooth HID Device + * profile. + * + * BluetoothHidDevice is a proxy object for controlling the Bluetooth HID + * Device Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothHidDevice proxy object. + * + * {@hide} */ public final class BluetoothHidDevice implements BluetoothProfile { @@ -62,7 +69,9 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Constants representing device subclass. * - * @see #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback) + * @see #registerApp + * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, + * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback) */ public static final byte SUBCLASS1_NONE = (byte) 0x00; public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40; @@ -80,9 +89,9 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Constants representing report types. * - * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int) - * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[]) - * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[]) + * @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int) + * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[]) + * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[]) */ public static final byte REPORT_TYPE_INPUT = (byte) 1; public static final byte REPORT_TYPE_OUTPUT = (byte) 2; @@ -91,7 +100,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Constants representing error response for Set Report. * - * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[]) + * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[]) */ public static final byte ERROR_RSP_SUCCESS = (byte) 0; public static final byte ERROR_RSP_NOT_READY = (byte) 1; @@ -104,7 +113,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { * Constants representing protocol mode used set by host. Default is always * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise. * - * @see BluetoothHidDeviceCallback#onSetProtocol(byte) + * @see BluetoothHidDeviceCallback#onSetProtocol(BluetoothDevice, byte) */ public static final byte PROTOCOL_BOOT_MODE = (byte) 0; public static final byte PROTOCOL_REPORT_MODE = (byte) 1; @@ -169,18 +178,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { public void onBluetoothStateChange(boolean up) { Log.d(TAG, "onBluetoothStateChange: up=" + up); synchronized (mConnection) { - if (!up) { - Log.d(TAG, "Unbinding service..."); - if (mService != null) { - mService = null; - try { - mContext.unbindService(mConnection); - } catch (IllegalArgumentException e) { - Log.e(TAG, "onBluetoothStateChange: could not unbind service:", - e); - } - } - } else { + if (up) { try { if (mService == null) { Log.d(TAG, "Binding HID Device service..."); @@ -189,14 +187,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { } catch (IllegalStateException e) { Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev " - + "service: ", - e); + + "service: ", e); } catch (SecurityException e) { Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev " - + "service: ", - e); + + "service: ", e); } + } else { + Log.d(TAG, "Unbinding service..."); + doUnbind(); } } } @@ -252,6 +251,18 @@ public final class BluetoothHidDevice implements BluetoothProfile { return true; } + void doUnbind() { + Log.d(TAG, "Unbinding HidDevService"); + if (mService != null) { + mService = null; + try { + mContext.unbindService(mConnection); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Unable to unbind HidDevService", e); + } + } + } + void close() { Log.v(TAG, "close()"); @@ -265,16 +276,8 @@ public final class BluetoothHidDevice implements BluetoothProfile { } synchronized (mConnection) { - if (mService != null) { - mService = null; - try { - mContext.unbindService(mConnection); - } catch (IllegalArgumentException e) { - Log.e(TAG, "close: could not unbind HID Dev service: ", e); - } - } + doUnbind(); } - mServiceListener = null; } @@ -388,7 +391,9 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Unregisters application. Active connection will be disconnected and no * new connections will be allowed until registered again using - * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)} + * {@link #registerApp + * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, + * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)} * * @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link * BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice, diff --git a/android/bluetooth/BluetoothHidDeviceAppConfiguration.java b/android/bluetooth/BluetoothHidDeviceAppConfiguration.java index 2731935a..d1efa2d6 100644 --- a/android/bluetooth/BluetoothHidDeviceAppConfiguration.java +++ b/android/bluetooth/BluetoothHidDeviceAppConfiguration.java @@ -21,7 +21,16 @@ import android.os.Parcelable; import java.util.Random; -/** @hide */ +/** + * Represents the app configuration for a Bluetooth HID Device application. + * + * The app needs a BluetoothHidDeviceAppConfiguration token to unregister + * the Bluetooth HID Device service. + * + * {@see BluetoothHidDevice} + * + * {@hide} + */ public final class BluetoothHidDeviceAppConfiguration implements Parcelable { private final long mHash; diff --git a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java index 1f80ed78..ccc3ef40 100644 --- a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java +++ b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java @@ -19,7 +19,17 @@ package android.bluetooth; import android.os.Parcel; import android.os.Parcelable; -/** @hide */ +/** + * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device + * application. + * + * The BluetoothHidDevice framework will update the L2CAP QoS settings for the + * app during registration. + * + * {@see BluetoothHidDevice} + * + * {@hide} + */ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { public final int serviceType; @@ -36,8 +46,7 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { public static final int MAX = (int) 0xffffffff; public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize, - int peakBandwidth, - int latency, int delayVariation) { + int peakBandwidth, int latency, int delayVariation) { this.serviceType = serviceType; this.tokenRate = tokenRate; this.tokenBucketSize = tokenBucketSize; @@ -66,10 +75,13 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { @Override public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) { - return new BluetoothHidDeviceAppQosSettings(in.readInt(), in.readInt(), + return new BluetoothHidDeviceAppQosSettings( + in.readInt(), + in.readInt(), + in.readInt(), in.readInt(), in.readInt(), - in.readInt(), in.readInt()); + in.readInt()); } @Override @@ -90,7 +102,7 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { /** @return an int array representation of this instance */ public int[] toArray() { - return new int[]{ + return new int[] { serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation }; } diff --git a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java index d21d5061..f01c4932 100644 --- a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java +++ b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java @@ -19,7 +19,18 @@ package android.bluetooth; import android.os.Parcel; import android.os.Parcelable; -/** @hide */ +/** + * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth + * HID Device application. + * + * The BluetoothHidDevice framework adds the SDP record during app + * registration, so that the Android device can be discovered as a Bluetooth + * HID Device. + * + * {@see BluetoothHidDevice} + * + * {@hide} + */ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { public final String name; @@ -57,8 +68,12 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { @Override public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) { - return new BluetoothHidDeviceAppSdpSettings(in.readString(), in.readString(), - in.readString(), in.readByte(), in.createByteArray()); + return new BluetoothHidDeviceAppSdpSettings( + in.readString(), + in.readString(), + in.readString(), + in.readByte(), + in.createByteArray()); } @Override diff --git a/android/bluetooth/BluetoothHidDeviceCallback.java b/android/bluetooth/BluetoothHidDeviceCallback.java index 3d407a6c..5ccda0dc 100644 --- a/android/bluetooth/BluetoothHidDeviceCallback.java +++ b/android/bluetooth/BluetoothHidDeviceCallback.java @@ -18,16 +18,24 @@ package android.bluetooth; import android.util.Log; -/** @hide */ +/** + * The template class that applications use to call callback functions on + * events from the HID host. Callback functions are wrapped in this class and + * registered to the Android system during app registration. + * + * {@see BluetoothHidDevice} + * + * {@hide} + */ public abstract class BluetoothHidDeviceCallback { - private static final String TAG = BluetoothHidDeviceCallback.class.getSimpleName(); + private static final String TAG = "BluetoothHidDevCallback"; /** * Callback called when application registration state changes. Usually it's * called due to either - * {@link BluetoothHidDevice#registerApp(String, String, String, byte, byte[], - * BluetoothHidDeviceCallback)} + * {@link BluetoothHidDevice#registerApp + * (String, String, String, byte, byte[], BluetoothHidDeviceCallback)} * or * {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)} * , but can be also unsolicited in case e.g. Bluetooth was turned off in @@ -79,7 +87,7 @@ public abstract class BluetoothHidDeviceCallback { /** * Callback called when SET_REPORT is received from remote host. In case * received data are invalid, application shall respond with - * {@link BluetoothHidDevice#reportError(BluetoothDevice)}. + * {@link BluetoothHidDevice#reportError(BluetoothDevice, byte)}. * * @param type Report Type. * @param id Report Id. diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java index d73f8526..20342807 100644 --- a/android/content/pm/ApplicationInfo.java +++ b/android/content/pm/ApplicationInfo.java @@ -375,7 +375,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext * traffic. Third-party libraries are encouraged to honor this flag as well. * - * <p>NOTE: {@code WebView} does not honor this flag. + * <p>NOTE: {@code WebView} honors this flag for applications targeting API level 26 and up. * * <p>This flag is ignored on Android N and above if an Android Network Security Config is * present. @@ -1463,98 +1463,84 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } - /** - * @hide - */ - public boolean isForwardLocked() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0; + /** @hide */ + public boolean isDefaultToDeviceProtectedStorage() { + return (privateFlags + & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0; } - /** - * @hide - */ - @TestApi - public boolean isSystemApp() { - return (flags & ApplicationInfo.FLAG_SYSTEM) != 0; + /** @hide */ + public boolean isDirectBootAware() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0; } - /** - * @hide - */ - @TestApi - public boolean isPrivilegedApp() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; + /** @hide */ + public boolean isEncryptionAware() { + return isDirectBootAware() || isPartiallyDirectBootAware(); } - /** - * @hide - */ - public boolean isUpdatedSystemApp() { - return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + /** @hide */ + public boolean isExternal() { + return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; } /** @hide */ - public boolean isInternal() { - return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0; + public boolean isExternalAsec() { + return TextUtils.isEmpty(volumeUuid) && isExternal(); } /** @hide */ - public boolean isExternalAsec() { - return TextUtils.isEmpty(volumeUuid) - && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + public boolean isForwardLocked() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0; } /** @hide */ - public boolean isDefaultToDeviceProtectedStorage() { - return (privateFlags - & ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0; + public boolean isInstantApp() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; } /** @hide */ - public boolean isDirectBootAware() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) != 0; + public boolean isInternal() { + return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0; } /** @hide */ - public boolean isPartiallyDirectBootAware() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0; + public boolean isOem() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0; } /** @hide */ - public boolean isEncryptionAware() { - return isDirectBootAware() || isPartiallyDirectBootAware(); + public boolean isPartiallyDirectBootAware() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0; } - /** - * @hide - */ - public boolean isInstantApp() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; + /** @hide */ + @TestApi + public boolean isPrivilegedApp() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } - /** - * @hide - */ + /** @hide */ public boolean isRequiredForSystemUser() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0; } - /** - * Returns true if the app has declared in its manifest that it wants its split APKs to be - * loaded into isolated Contexts, with their own ClassLoaders and Resources objects. - * @hide - */ - public boolean requestsIsolatedSplitLoading() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0; - } - - /** - * @hide - */ + /** @hide */ public boolean isStaticSharedLibrary() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0; } + /** @hide */ + @TestApi + public boolean isSystemApp() { + return (flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + /** @hide */ + public boolean isUpdatedSystemApp() { + return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + /** * Returns whether or not this application was installed as a virtual preload. */ @@ -1563,10 +1549,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** + * Returns true if the app has declared in its manifest that it wants its split APKs to be + * loaded into isolated Contexts, with their own ClassLoaders and Resources objects. * @hide */ - public boolean isOem() { - return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0; + public boolean requestsIsolatedSplitLoading() { + return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0; } /** diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java index 143c51da..14cf8557 100644 --- a/android/content/pm/PackageManagerInternal.java +++ b/android/content/pm/PackageManagerInternal.java @@ -337,6 +337,12 @@ public abstract class PackageManagerInternal { public abstract boolean isPackagePersistent(String packageName); /** + * Returns whether or not the given package represents a legacy system application released + * prior to runtime permissions. + */ + public abstract boolean isLegacySystemApp(PackageParser.Package pkg); + + /** * Get all overlay packages for a user. * @param userId The user for which to get the overlays. * @return A list of overlay packages. An empty list is returned if the @@ -467,7 +473,4 @@ public abstract class PackageManagerInternal { /** Updates the flags for the given permission. */ public abstract void updatePermissionFlagsTEMP(@NonNull String permName, @NonNull String packageName, int flagMask, int flagValues, int userId); - /** Returns a PermissionGroup. */ - public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP( - @NonNull String groupName); } diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java index ad36139a..b48829cf 100644 --- a/android/content/pm/PackageParser.java +++ b/android/content/pm/PackageParser.java @@ -6221,48 +6221,48 @@ public class PackageParser { return false; } - /** - * @hide - */ + /** @hide */ + public boolean isExternal() { + return applicationInfo.isExternal(); + } + + /** @hide */ public boolean isForwardLocked() { return applicationInfo.isForwardLocked(); } - /** - * @hide - */ - public boolean isSystemApp() { - return applicationInfo.isSystemApp(); + /** @hide */ + public boolean isOem() { + return applicationInfo.isOem(); } - /** - * @hide - */ - public boolean isPrivilegedApp() { + /** @hide */ + public boolean isPrivileged() { return applicationInfo.isPrivilegedApp(); } - /** - * @hide - */ + /** @hide */ + public boolean isSystem() { + return applicationInfo.isSystemApp(); + } + + /** @hide */ public boolean isUpdatedSystemApp() { return applicationInfo.isUpdatedSystemApp(); } - /** - * @hide - */ + /** @hide */ public boolean canHaveOatDir() { // The following app types CANNOT have oat directory // - non-updated system apps // - forward-locked apps or apps installed in ASEC containers - return (!isSystemApp() || isUpdatedSystemApp()) + return (!isSystem() || isUpdatedSystemApp()) && !isForwardLocked() && !applicationInfo.isExternalAsec(); } public boolean isMatch(int flags) { if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { - return isSystemApp(); + return isSystem(); } return true; } diff --git a/android/content/res/Resources_Delegate.java b/android/content/res/Resources_Delegate.java index c1e9cd36..d9c97fe2 100644 --- a/android/content/res/Resources_Delegate.java +++ b/android/content/res/Resources_Delegate.java @@ -651,6 +651,26 @@ public class Resources_Delegate { } @LayoutlibDelegate + static float getFloat(Resources resources, int id) { + Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); + + if (value != null) { + ResourceValue resValue = value.getSecond(); + + if (resValue != null) { + String v = resValue.getValue(); + if (v != null) { + try { + return Float.parseFloat(v); + } catch (NumberFormatException ignore) { + } + } + } + } + return 0; + } + + @LayoutlibDelegate static boolean getBoolean(Resources resources, int id) throws NotFoundException { Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); diff --git a/android/hardware/location/ContextHubClient.java b/android/hardware/location/ContextHubClient.java new file mode 100644 index 00000000..b7e353a4 --- /dev/null +++ b/android/hardware/location/ContextHubClient.java @@ -0,0 +1,95 @@ +/* + * 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 android.hardware.location; + +import android.annotation.RequiresPermission; +import android.os.Handler; + +import java.io.Closeable; + +/** + * A class describing a client of the Context Hub Service. + * + * Clients can send messages to nanoapps at a Context Hub through this object. + * + * @hide + */ +public class ContextHubClient implements Closeable { + /* + * The ContextHubClient interface associated with this client. + */ + // TODO: Implement this interface and associate with ContextHubClient object + // private final IContextHubClient mClientInterface; + + /* + * The listening callback associated with this client. + */ + private ContextHubClientCallback mCallback; + + /* + * The Context Hub that this client is attached to. + */ + private ContextHubInfo mAttachedHub; + + /* + * The handler to invoke mCallback. + */ + private Handler mCallbackHandler; + + ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) { + mCallback = callback; + mCallbackHandler = handler; + mAttachedHub = hubInfo; + } + + /** + * Returns the hub that this client is attached to. + * + * @return the ContextHubInfo of the attached hub + */ + public ContextHubInfo getAttachedHub() { + return mAttachedHub; + } + + /** + * Closes the connection for this client and the Context Hub Service. + * + * When this function is invoked, the messaging associated with this client is invalidated. + * All futures messages targeted for this client are dropped at the service. + */ + public void close() { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + /** + * Sends a message to a nanoapp through the Context Hub Service. + * + * This function returns TRANSACTION_SUCCESS if the message has reached the HAL, but + * does not guarantee delivery of the message to the target nanoapp. + * + * @param message the message object to send + * + * @return the result of sending the message defined as in ContextHubTransaction.Result + * + * @see NanoAppMessage + * @see ContextHubTransaction.Result + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + @ContextHubTransaction.Result + public int sendMessageToNanoApp(NanoAppMessage message) { + throw new UnsupportedOperationException("TODO: Implement this"); + } +} diff --git a/android/hardware/location/ContextHubClientCallback.java b/android/hardware/location/ContextHubClientCallback.java new file mode 100644 index 00000000..ab19d547 --- /dev/null +++ b/android/hardware/location/ContextHubClientCallback.java @@ -0,0 +1,85 @@ +/* + * 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 android.hardware.location; + +/** + * A class for {@link android.hardware.location.ContextHubClient ContextHubClient} to + * receive messages and life-cycle events from nanoapps in the Context Hub at which the client is + * attached to. + * + * This callback is registered through the + * {@link android.hardware.location.ContextHubManager#createClient() creation} of + * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are + * invoked in the following ways: + * 1) Messages from nanoapps delivered through onMessageFromNanoApp may either be broadcasted + * or targeted to a specific client. + * 2) Nanoapp or Context Hub events (the remaining callbacks) are broadcasted to all clients, and + * the client can choose to ignore the event by filtering through the parameters. + * + * @hide + */ +public class ContextHubClientCallback { + /** + * Callback invoked when receiving a message from a nanoapp. + * + * The message contents of this callback may either be broadcasted or targeted to the + * client receiving the invocation. + * + * @param message the message sent by the nanoapp + */ + public void onMessageFromNanoApp(NanoAppMessage message) {} + + /** + * Callback invoked when the attached Context Hub has reset. + */ + public void onHubReset() {} + + /** + * Callback invoked when a nanoapp aborts at the attached Context Hub. + * + * @param nanoAppId the ID of the nanoapp that had aborted + * @param abortCode the reason for nanoapp's abort, specific to each nanoapp + */ + public void onNanoAppAborted(long nanoAppId, int abortCode) {} + + /** + * Callback invoked when a nanoapp is loaded at the attached Context Hub. + * + * @param nanoAppId the ID of the nanoapp that had been loaded + */ + public void onNanoAppLoaded(long nanoAppId) {} + + /** + * Callback invoked when a nanoapp is unloaded from the attached Context Hub. + * + * @param nanoAppId the ID of the nanoapp that had been unloaded + */ + public void onNanoAppUnloaded(long nanoAppId) {} + + /** + * Callback invoked when a nanoapp is enabled at the attached Context Hub. + * + * @param nanoAppId the ID of the nanoapp that had been enabled + */ + public void onNanoAppEnabled(long nanoAppId) {} + + /** + * Callback invoked when a nanoapp is disabled at the attached Context Hub. + * + * @param nanoAppId the ID of the nanoapp that had been disabled + */ + public void onNanoAppDisabled(long nanoAppId) {} +} diff --git a/android/hardware/location/ContextHubManager.java b/android/hardware/location/ContextHubManager.java index 60500463..7cbb436c 100644 --- a/android/hardware/location/ContextHubManager.java +++ b/android/hardware/location/ContextHubManager.java @@ -15,6 +15,7 @@ */ package android.hardware.location; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -27,6 +28,8 @@ import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.Log; +import java.util.List; + /** * A class that exposes the Context hubs on a device to applications. * @@ -38,7 +41,6 @@ import android.util.Log; @SystemApi @SystemService(Context.CONTEXTHUB_SERVICE) public final class ContextHubManager { - private static final String TAG = "ContextHubManager"; private final Looper mMainLooper; @@ -256,6 +258,100 @@ public final class ContextHubManager { } /** + * Returns the list of context hubs in the system. + * + * @return the list of context hub informations + * + * @see ContextHubInfo + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public List<ContextHubInfo> getContextHubs() { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + /** + * Loads a nanoapp at the specified Context Hub. + * + * After the nanoapp binary is successfully loaded at the specified hub, the nanoapp will be in + * the enabled state. + * + * @param hubInfo the hub to load the nanoapp on + * @param appBinary The app binary to load + * + * @return the ContextHubTransaction of the request + * + * @see NanoAppBinary + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public ContextHubTransaction<Void> loadNanoApp( + ContextHubInfo hubInfo, NanoAppBinary appBinary) { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + /** + * Unloads a nanoapp at the specified Context Hub. + * + * @param hubInfo the hub to unload the nanoapp from + * @param nanoAppId the app to unload + * + * @return the ContextHubTransaction of the request + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + /** + * Enables a nanoapp at the specified Context Hub. + * + * @param hubInfo the hub to enable the nanoapp on + * @param nanoAppId the app to enable + * + * @return the ContextHubTransaction of the request + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + /** + * Disables a nanoapp at the specified Context Hub. + * + * @param hubInfo the hub to disable the nanoapp on + * @param nanoAppId the app to disable + * + * @return the ContextHubTransaction of the request + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + /** + * Requests a query for nanoapps loaded at the specified Context Hub. + * + * @param hubInfo the hub to query a list of nanoapps from + * + * @return the ContextHubTransaction of the request + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) + public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + /** * Set a callback to receive messages from the context hub * * @param callback Callback object @@ -307,6 +403,29 @@ public final class ContextHubManager { } /** + * Creates and registers a client and its callback with the Context Hub Service. + * + * A client is registered with the Context Hub Service for a specified Context Hub. When the + * registration succeeds, the client can send messages to nanoapps through the returned + * {@link ContextHubClient} object, and receive notifications through the provided callback. + * + * @param callback the notification callback to register + * @param hubInfo the hub to attach this client to + * @param handler the handler to invoke the callback, if null uses the current thread Looper + * + * @return the registered client object + * + * @see ContextHubClientCallback + * + * @hide + */ + public ContextHubClient createClient( + ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) { + throw new UnsupportedOperationException( + "TODO: Implement this, and throw an exception on error"); + } + + /** * Unregister a callback for receive messages from the context hub. * * @see Callback diff --git a/android/hardware/location/ContextHubTransaction.java b/android/hardware/location/ContextHubTransaction.java new file mode 100644 index 00000000..4877d38b --- /dev/null +++ b/android/hardware/location/ContextHubTransaction.java @@ -0,0 +1,229 @@ +/* + * 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 android.hardware.location; + +import android.annotation.IntDef; +import android.os.Handler; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.TimeUnit; + +/** + * A class describing a request sent to the Context Hub Service. + * + * This object is generated as a result of an asynchronous request sent to the Context Hub + * through the ContextHubManager APIs. The caller can either retrieve the result + * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or + * asynchronously through a user-defined callback + * ({@link #onComplete(ContextHubTransaction.Callback<T>, Handler)}). + * + * A transaction can be invalidated if the caller of the transaction is no longer active + * and the reference to this object is lost, or if timeout period has passed in + * {@link #waitForResponse(long, TimeUnit)}. + * + * @param <T> the type of the contents in the transaction response + * + * @hide + */ +public class ContextHubTransaction<T> { + /** + * Constants describing the type of a transaction through the Context Hub Service. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TYPE_LOAD_NANOAPP, + TYPE_UNLOAD_NANOAPP, + TYPE_ENABLE_NANOAPP, + TYPE_DISABLE_NANOAPP, + TYPE_QUERY_NANOAPPS}) + public @interface Type {} + public static final int TYPE_LOAD_NANOAPP = 0; + public static final int TYPE_UNLOAD_NANOAPP = 1; + public static final int TYPE_ENABLE_NANOAPP = 2; + public static final int TYPE_DISABLE_NANOAPP = 3; + public static final int TYPE_QUERY_NANOAPPS = 4; + + /** + * Constants describing the result of a transaction or request through the Context Hub Service. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TRANSACTION_SUCCESS, + TRANSACTION_FAILED_UNKNOWN, + TRANSACTION_FAILED_BAD_PARAMS, + TRANSACTION_FAILED_UNINITIALIZED, + TRANSACTION_FAILED_PENDING, + TRANSACTION_FAILED_AT_HUB, + TRANSACTION_FAILED_TIMEOUT}) + public @interface Result {} + public static final int TRANSACTION_SUCCESS = 0; + /** + * Generic failure mode. + */ + public static final int TRANSACTION_FAILED_UNKNOWN = 1; + /** + * Failure mode when the request parameters were not valid. + */ + public static final int TRANSACTION_FAILED_BAD_PARAMS = 2; + /** + * Failure mode when the Context Hub is not initialized. + */ + public static final int TRANSACTION_FAILED_UNINITIALIZED = 3; + /** + * Failure mode when there are too many transactions pending. + */ + public static final int TRANSACTION_FAILED_PENDING = 4; + /** + * Failure mode when the request went through, but failed asynchronously at the hub. + */ + public static final int TRANSACTION_FAILED_AT_HUB = 5; + /** + * Failure mode when the transaction has timed out. + */ + public static final int TRANSACTION_FAILED_TIMEOUT = 6; + + /** + * A class describing the response for a ContextHubTransaction. + * + * @param <R> the type of the contents in the response + */ + public static class Response<R> { + /* + * The result of the transaction. + */ + @ContextHubTransaction.Result + private int mResult; + + /* + * The contents of the response from the Context Hub. + */ + private R mContents; + + Response(@ContextHubTransaction.Result int result, R contents) { + mResult = result; + mContents = contents; + } + + @ContextHubTransaction.Result + public int getResult() { + return mResult; + } + + public R getContents() { + return mContents; + } + } + + /** + * An interface describing the callback to be invoked when a transaction completes. + * + * @param <C> the type of the contents in the transaction response + */ + @FunctionalInterface + public interface Callback<C> { + /** + * The callback to invoke when the transaction completes. + * + * @param transaction the transaction that this callback was attached to. + * @param response the response of the transaction. + */ + void onComplete( + ContextHubTransaction<C> transaction, ContextHubTransaction.Response<C> response); + } + + /* + * The unique identifier representing the transaction. + */ + private int mTransactionId; + + /* + * The type of the transaction. + */ + @Type + private int mTransactionType; + + /* + * The response of the transaction. + */ + private ContextHubTransaction.Response<T> mResponse; + + /* + * The handler to invoke the aynsc response supplied by onComplete. + */ + private Handler mHandler = null; + + /* + * The callback to invoke when the transaction completes. + */ + private ContextHubTransaction.Callback<T> mCallback = null; + + ContextHubTransaction(int id, @Type int type) { + mTransactionId = id; + mTransactionType = type; + } + + /** + * @return the type of the transaction + */ + @Type + public int getType() { + return mTransactionType; + } + + /** + * Waits to receive the asynchronous transaction result. + * + * This function blocks until the Context Hub Service has received a response + * for the transaction represented by this object by the Context Hub, or a + * specified timeout period has elapsed. + * + * If the specified timeout has passed, the transaction represented by this object + * is invalidated by the Context Hub Service (resulting in a timeout failure in the + * response). + * + * @param timeout the timeout duration + * @param unit the unit of the timeout + * + * @return the transaction response + */ + public ContextHubTransaction.Response<T> waitForResponse(long timeout, TimeUnit unit) { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + /** + * Sets a callback to be invoked when the transaction completes. + * + * This function provides an asynchronous approach to retrieve the result of the + * transaction. When the transaction response has been provided by the Context Hub, + * the given callback will be posted by the provided handler. + * + * If the transaction has already completed at the time of invocation, the callback + * will be immediately posted by the handler. If the transaction has been invalidated, + * the callback will never be invoked. + * + * @param callback the callback to be invoked upon completion + * @param handler the handler to post the callback + */ + public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) { + throw new UnsupportedOperationException("TODO: Implement this"); + } + + private void setResponse(ContextHubTransaction.Response<T> response) { + mResponse = response; + throw new UnsupportedOperationException("TODO: Unblock waitForResponse"); + } +} diff --git a/android/hardware/location/NanoAppBinary.java b/android/hardware/location/NanoAppBinary.java new file mode 100644 index 00000000..54542277 --- /dev/null +++ b/android/hardware/location/NanoAppBinary.java @@ -0,0 +1,198 @@ +/* + * 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 android.hardware.location; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * @hide + */ +public final class NanoAppBinary implements Parcelable { + private static final String TAG = "NanoAppBinary"; + + /* + * The contents of the app binary. + */ + private byte[] mNanoAppBinary; + + /* + * Contents of the nanoapp binary header. + * + * Only valid if mHasValidHeader is true. + * See nano_app_binary_t in context_hub.h for details. + */ + private int mHeaderVersion; + private int mMagic; + private long mNanoAppId; + private int mNanoAppVersion; + private int mFlags; + private long mHwHubType; + private byte mTargetChreApiMajorVersion; + private byte mTargetChreApiMinorVersion; + + private boolean mHasValidHeader = false; + + /* + * The header version used to parse the binary in parseBinaryHeader(). + */ + private static final int EXPECTED_HEADER_VERSION = 1; + + /* + * The magic value expected in the header. + */ + private static final int EXPECTED_MAGIC_VALUE = + (((int) 'N' << 0) | ((int) 'A' << 8) | ((int) 'N' << 16) | ((int) 'O' << 24)); + + /* + * Byte order established in context_hub.h + */ + private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN; + + public NanoAppBinary(byte[] appBinary) { + mNanoAppBinary = appBinary; + parseBinaryHeader(); + } + + /* + * Parses the binary header and populates its field using mNanoAppBinary. + */ + private void parseBinaryHeader() { + ByteBuffer buf = ByteBuffer.wrap(mNanoAppBinary).order(HEADER_ORDER); + + mHasValidHeader = false; + try { + mHeaderVersion = buf.getInt(); + if (mHeaderVersion != EXPECTED_HEADER_VERSION) { + Log.e(TAG, "Unexpected header version " + mHeaderVersion + " while parsing header" + + " (expected " + EXPECTED_HEADER_VERSION + ")"); + return; + } + + mMagic = buf.getInt(); + mNanoAppId = buf.getLong(); + mNanoAppVersion = buf.getInt(); + mFlags = buf.getInt(); + mHwHubType = buf.getLong(); + mTargetChreApiMajorVersion = buf.get(); + mTargetChreApiMinorVersion = buf.get(); + } catch (BufferUnderflowException e) { + Log.e(TAG, "Not enough contents in nanoapp header"); + return; + } + + if (mMagic != EXPECTED_MAGIC_VALUE) { + Log.e(TAG, "Unexpected magic value " + String.format("0x%08X", mMagic) + + "while parsing header (expected " + + String.format("0x%08X", EXPECTED_MAGIC_VALUE) + ")"); + } else { + mHasValidHeader = true; + } + } + + /** + * @return the app binary byte array + */ + public byte[] getNanoAppBinary() { + return mNanoAppBinary; + } + + /** + * @return {@code true} if the header is valid, {@code false} otherwise + */ + public boolean hasValidHeader() { + return mHasValidHeader; + } + + /** + * @return the header version + */ + public int getHeaderVersion() { + return mHeaderVersion; + } + + /** + * @return the app ID parsed from the nanoapp header + */ + public long getNanoAppId() { + return mNanoAppId; + } + + /** + * @return the app version parsed from the nanoapp header + */ + public int getNanoAppVersion() { + return mNanoAppVersion; + } + + /** + * @return the compile target hub type parsed from the nanoapp header + */ + public long getHwHubType() { + return mHwHubType; + } + + /** + * @return the target CHRE API major version parsed from the nanoapp header + */ + public byte getTargetChreApiMajorVersion() { + return mTargetChreApiMajorVersion; + } + + /** + * @return the target CHRE API minor version parsed from the nanoapp header + */ + public byte getTargetChreApiMinorVersion() { + return mTargetChreApiMinorVersion; + } + + private NanoAppBinary(Parcel in) { + int binaryLength = in.readInt(); + mNanoAppBinary = new byte[binaryLength]; + in.readByteArray(mNanoAppBinary); + + parseBinaryHeader(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mNanoAppBinary.length); + out.writeByteArray(mNanoAppBinary); + } + + public static final Creator<NanoAppBinary> CREATOR = + new Creator<NanoAppBinary>() { + @Override + public NanoAppBinary createFromParcel(Parcel in) { + return new NanoAppBinary(in); + } + + @Override + public NanoAppBinary[] newArray(int size) { + return new NanoAppBinary[size]; + } + }; +} diff --git a/android/hardware/location/NanoAppMessage.java b/android/hardware/location/NanoAppMessage.java new file mode 100644 index 00000000..20286749 --- /dev/null +++ b/android/hardware/location/NanoAppMessage.java @@ -0,0 +1,143 @@ +/* + * 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 android.hardware.location; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class describing messages send to or from nanoapps through the Context Hub Service. + * + * The basis of the class is in the IContextHub.hal ContextHubMsg definition. + * + * @hide + */ +public final class NanoAppMessage implements Parcelable { + private long mNanoAppId; + private int mMessageType; + private byte[] mMessageBody; + private boolean mIsBroadcasted; + + private NanoAppMessage( + long nanoAppId, int messageType, byte[] messageBody, boolean broadcasted) { + mNanoAppId = nanoAppId; + mMessageType = messageType; + mMessageBody = messageBody; + mIsBroadcasted = broadcasted; + } + + /** + * Creates a NanoAppMessage object to send to a nanoapp. + * + * This factory method can be used to generate a NanoAppMessage object to be used in + * the ContextHubClient.sendMessageToNanoApp API. + * + * @param targetNanoAppId the ID of the nanoapp to send the message to + * @param messageType the nanoapp-dependent message type + * @param messageBody the byte array message contents + * + * @return the NanoAppMessage object + */ + public static NanoAppMessage createMessageToNanoApp( + long targetNanoAppId, int messageType, byte[] messageBody) { + return new NanoAppMessage( + targetNanoAppId, messageType, messageBody, false /* broadcasted */); + } + + /** + * Creates a NanoAppMessage object sent from a nanoapp. + * + * This factory method is intended only to be used by the Context Hub Service when delivering + * messages from a nanoapp to clients. + * + * @param sourceNanoAppId the ID of the nanoapp that the message was sent from + * @param messageType the nanoapp-dependent message type + * @param messageBody the byte array message contents + * @param broadcasted {@code true} if the message was broadcasted, {@code false} otherwise + * + * @return the NanoAppMessage object + */ + public static NanoAppMessage createMessageFromNanoApp( + long sourceNanoAppId, int messageType, byte[] messageBody, boolean broadcasted) { + return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted); + } + + /** + * @return the ID of the source or destination nanoapp + */ + public long getNanoAppId() { + return mNanoAppId; + } + + /** + * @return the type of the message that is nanoapp-dependent + */ + public int getMessageType() { + return mMessageType; + } + + /** + * @return the byte array contents of the message + */ + public byte[] getMessageBody() { + return mMessageBody; + } + + /** + * @return {@code true} if the message is broadcasted, {@code false} otherwise + */ + public boolean isBroadcastMessage() { + return mIsBroadcasted; + } + + private NanoAppMessage(Parcel in) { + mNanoAppId = in.readLong(); + mIsBroadcasted = (in.readInt() == 1); + mMessageType = in.readInt(); + + int msgSize = in.readInt(); + mMessageBody = new byte[msgSize]; + in.readByteArray(mMessageBody); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mNanoAppId); + out.writeInt(mIsBroadcasted ? 1 : 0); + out.writeInt(mMessageType); + + out.writeInt(mMessageBody.length); + out.writeByteArray(mMessageBody); + } + + public static final Creator<NanoAppMessage> CREATOR = + new Creator<NanoAppMessage>() { + @Override + public NanoAppMessage createFromParcel(Parcel in) { + return new NanoAppMessage(in); + } + + @Override + public NanoAppMessage[] newArray(int size) { + return new NanoAppMessage[size]; + } + }; +} diff --git a/android/hardware/location/NanoAppState.java b/android/hardware/location/NanoAppState.java new file mode 100644 index 00000000..644031b0 --- /dev/null +++ b/android/hardware/location/NanoAppState.java @@ -0,0 +1,88 @@ +/* + * 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 android.hardware.location; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class describing the nanoapp state information resulting from a query to a Context Hub. + * + * @hide + */ +public final class NanoAppState implements Parcelable { + private long mNanoAppId; + private int mNanoAppVersion; + private boolean mIsEnabled; + + public NanoAppState(long nanoAppId, int appVersion, boolean enabled) { + mNanoAppId = nanoAppId; + mNanoAppVersion = appVersion; + mIsEnabled = enabled; + } + + /** + * @return the NanoAppInfo for this app + */ + public long getNanoAppId() { + return mNanoAppId; + } + + /** + * @return the app version + */ + public long getNanoAppVersion() { + return mNanoAppVersion; + } + + /** + * @return {@code true} if the app is enabled at the Context Hub, {@code false} otherwise + */ + public boolean isEnabled() { + return mIsEnabled; + } + + private NanoAppState(Parcel in) { + mNanoAppId = in.readLong(); + mNanoAppVersion = in.readInt(); + mIsEnabled = (in.readInt() == 1); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mNanoAppId); + out.writeInt(mNanoAppVersion); + out.writeInt(mIsEnabled ? 1 : 0); + } + + public static final Creator<NanoAppState> CREATOR = + new Creator<NanoAppState>() { + @Override + public NanoAppState createFromParcel(Parcel in) { + return new NanoAppState(in); + } + + @Override + public NanoAppState[] newArray(int size) { + return new NanoAppState[size]; + } + }; +} diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java index 595d8571..996824d4 100644 --- a/android/hardware/usb/UsbManager.java +++ b/android/hardware/usb/UsbManager.java @@ -102,7 +102,7 @@ public class UsbManager { "android.hardware.usb.action.USB_PORT_CHANGED"; /** - * Broadcast Action: A broadcast for USB device attached event. + * Activity intent sent when a USB device is attached. * * This intent is sent when a USB device is attached to the USB bus when in host mode. * <ul> @@ -128,9 +128,8 @@ public class UsbManager { "android.hardware.usb.action.USB_DEVICE_DETACHED"; /** - * Broadcast Action: A broadcast for USB accessory attached event. + * Activity intent sent when a USB accessory is attached. * - * This intent is sent when a USB accessory is attached. * <ul> * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory} * for the attached accessory diff --git a/android/location/GnssClock.java b/android/location/GnssClock.java index 25d247de..671c57cb 100644 --- a/android/location/GnssClock.java +++ b/android/location/GnssClock.java @@ -332,6 +332,9 @@ public final class GnssClock implements Parcelable { /** * Gets the clock's Drift in nanoseconds per second. * + * <p>This value is the instantaneous time-derivative of the value provided by + * {@link #getBiasNanos()}. + * * <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master * clock) frequency. The error estimate for this reported drift is * {@link #getDriftUncertaintyNanosPerSecond()}. diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java index 4bb88440..db12dd97 100644 --- a/android/net/NetworkCapabilities.java +++ b/android/net/NetworkCapabilities.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; @@ -23,6 +24,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.BitUtils; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.StringJoiner; @@ -77,6 +80,31 @@ public final class NetworkCapabilities implements Parcelable { */ private long mNetworkCapabilities; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "NET_CAPABILITY_" }, value = { + NET_CAPABILITY_MMS, + NET_CAPABILITY_SUPL, + NET_CAPABILITY_DUN, + NET_CAPABILITY_FOTA, + NET_CAPABILITY_IMS, + NET_CAPABILITY_CBS, + NET_CAPABILITY_WIFI_P2P, + NET_CAPABILITY_IA, + NET_CAPABILITY_RCS, + NET_CAPABILITY_XCAP, + NET_CAPABILITY_EIMS, + NET_CAPABILITY_NOT_METERED, + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_NOT_RESTRICTED, + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_VALIDATED, + NET_CAPABILITY_CAPTIVE_PORTAL, + NET_CAPABILITY_FOREGROUND, + }) + public @interface NetCapability { } + /** * Indicates this is a network that has the ability to reach the * carrier's MMSC for sending and receiving MMS messages. @@ -260,11 +288,11 @@ public final class NetworkCapabilities implements Parcelable { * Multiple capabilities may be applied sequentially. Note that when searching * for a network to satisfy a request, all capabilities requested must be satisfied. * - * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added. + * @param capability the capability to be added. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public NetworkCapabilities addCapability(int capability) { + public NetworkCapabilities addCapability(@NetCapability int capability) { if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { throw new IllegalArgumentException("NetworkCapability out of range"); } @@ -275,11 +303,11 @@ public final class NetworkCapabilities implements Parcelable { /** * Removes (if found) the given capability from this {@code NetworkCapability} instance. * - * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed. + * @param capability the capability to be removed. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public NetworkCapabilities removeCapability(int capability) { + public NetworkCapabilities removeCapability(@NetCapability int capability) { if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { throw new IllegalArgumentException("NetworkCapability out of range"); } @@ -290,21 +318,20 @@ public final class NetworkCapabilities implements Parcelable { /** * Gets all the capabilities set on this {@code NetworkCapability} instance. * - * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values - * for this instance. + * @return an array of capability values for this instance. * @hide */ - public int[] getCapabilities() { + public @NetCapability int[] getCapabilities() { return BitUtils.unpackBits(mNetworkCapabilities); } /** * Tests for the presence of a capabilitity on this instance. * - * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for. + * @param capability the capabilities to be tested for. * @return {@code true} if set on this instance. */ - public boolean hasCapability(int capability) { + public boolean hasCapability(@NetCapability int capability) { if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) { return false; } @@ -385,6 +412,19 @@ public final class NetworkCapabilities implements Parcelable { */ private long mTransportTypes; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TRANSPORT_" }, value = { + TRANSPORT_CELLULAR, + TRANSPORT_WIFI, + TRANSPORT_BLUETOOTH, + TRANSPORT_ETHERNET, + TRANSPORT_VPN, + TRANSPORT_WIFI_AWARE, + TRANSPORT_LOWPAN, + }) + public @interface Transport { } + /** * Indicates this network uses a Cellular transport. */ @@ -426,7 +466,7 @@ public final class NetworkCapabilities implements Parcelable { public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN; /** @hide */ - public static boolean isValidTransport(int transportType) { + public static boolean isValidTransport(@Transport int transportType) { return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT); } @@ -449,11 +489,11 @@ public final class NetworkCapabilities implements Parcelable { * to be selected. This is logically different than * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above. * - * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added. + * @param transportType the transport type to be added. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public NetworkCapabilities addTransportType(int transportType) { + public NetworkCapabilities addTransportType(@Transport int transportType) { checkValidTransportType(transportType); mTransportTypes |= 1 << transportType; setNetworkSpecifier(mNetworkSpecifier); // used for exception checking @@ -463,11 +503,11 @@ public final class NetworkCapabilities implements Parcelable { /** * Removes (if found) the given transport from this {@code NetworkCapability} instance. * - * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed. + * @param transportType the transport type to be removed. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public NetworkCapabilities removeTransportType(int transportType) { + public NetworkCapabilities removeTransportType(@Transport int transportType) { checkValidTransportType(transportType); mTransportTypes &= ~(1 << transportType); setNetworkSpecifier(mNetworkSpecifier); // used for exception checking @@ -477,21 +517,20 @@ public final class NetworkCapabilities implements Parcelable { /** * Gets all the transports set on this {@code NetworkCapability} instance. * - * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values - * for this instance. + * @return an array of transport type values for this instance. * @hide */ - public int[] getTransportTypes() { + public @Transport int[] getTransportTypes() { return BitUtils.unpackBits(mTransportTypes); } /** * Tests for the presence of a transport on this instance. * - * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for. + * @param transportType the transport type to be tested for. * @return {@code true} if set on this instance. */ - public boolean hasTransport(int transportType) { + public boolean hasTransport(@Transport int transportType) { return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0); } @@ -896,7 +935,7 @@ public final class NetworkCapabilities implements Parcelable { /** * @hide */ - public static String capabilityNamesOf(int[] capabilities) { + public static String capabilityNamesOf(@NetCapability int[] capabilities) { StringJoiner joiner = new StringJoiner("|"); if (capabilities != null) { for (int c : capabilities) { @@ -909,7 +948,7 @@ public final class NetworkCapabilities implements Parcelable { /** * @hide */ - public static String capabilityNameOf(int capability) { + public static String capabilityNameOf(@NetCapability int capability) { switch (capability) { case NET_CAPABILITY_MMS: return "MMS"; case NET_CAPABILITY_SUPL: return "SUPL"; @@ -937,7 +976,7 @@ public final class NetworkCapabilities implements Parcelable { /** * @hide */ - public static String transportNamesOf(int[] types) { + public static String transportNamesOf(@Transport int[] types) { StringJoiner joiner = new StringJoiner("|"); if (types != null) { for (int t : types) { @@ -950,14 +989,14 @@ public final class NetworkCapabilities implements Parcelable { /** * @hide */ - public static String transportNameOf(int transport) { + public static String transportNameOf(@Transport int transport) { if (!isValidTransport(transport)) { return "UNKNOWN"; } return TRANSPORT_NAMES[transport]; } - private static void checkValidTransportType(int transport) { + private static void checkValidTransportType(@Transport int transport) { Preconditions.checkArgument( isValidTransport(transport), "Invalid TransportType " + transport); } diff --git a/android/net/NetworkRequest.java b/android/net/NetworkRequest.java index 95a8bb47..25b17052 100644 --- a/android/net/NetworkRequest.java +++ b/android/net/NetworkRequest.java @@ -155,14 +155,13 @@ public class NetworkRequest implements Parcelable { * Add the given capability requirement to this builder. These represent * the requested network's required capabilities. Note that when searching * for a network to satisfy a request, all capabilities requested must be - * satisfied. See {@link NetworkCapabilities} for {@code NET_CAPABILITY_*} - * definitions. + * satisfied. * - * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add. + * @param capability The capability to add. * @return The builder to facilitate chaining * {@code builder.addCapability(...).addCapability();}. */ - public Builder addCapability(int capability) { + public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addCapability(capability); return this; } @@ -170,10 +169,10 @@ public class NetworkRequest implements Parcelable { /** * Removes (if found) the given capability from this builder instance. * - * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove. + * @param capability The capability to remove. * @return The builder to facilitate chaining. */ - public Builder removeCapability(int capability) { + public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeCapability(capability); return this; } @@ -208,13 +207,12 @@ public class NetworkRequest implements Parcelable { * Adds the given transport requirement to this builder. These represent * the set of allowed transports for the request. Only networks using one * of these transports will satisfy the request. If no particular transports - * are required, none should be specified here. See {@link NetworkCapabilities} - * for {@code TRANSPORT_*} definitions. + * are required, none should be specified here. * - * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add. + * @param transportType The transport type to add. * @return The builder to facilitate chaining. */ - public Builder addTransportType(int transportType) { + public Builder addTransportType(@NetworkCapabilities.Transport int transportType) { mNetworkCapabilities.addTransportType(transportType); return this; } @@ -222,10 +220,10 @@ public class NetworkRequest implements Parcelable { /** * Removes (if found) the given transport from this builder instance. * - * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove. + * @param transportType The transport type to remove. * @return The builder to facilitate chaining. */ - public Builder removeTransportType(int transportType) { + public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) { mNetworkCapabilities.removeTransportType(transportType); return this; } diff --git a/android/net/metrics/DefaultNetworkEvent.java b/android/net/metrics/DefaultNetworkEvent.java index 28cf42f2..eb61c153 100644 --- a/android/net/metrics/DefaultNetworkEvent.java +++ b/android/net/metrics/DefaultNetworkEvent.java @@ -16,73 +16,43 @@ package android.net.metrics; +import static android.net.ConnectivityManager.NETID_UNSET; + import android.net.NetworkCapabilities; -import android.os.Parcel; -import android.os.Parcelable; /** * An event recorded by ConnectivityService when there is a change in the default network. * {@hide} */ -public final class DefaultNetworkEvent implements Parcelable { +public class DefaultNetworkEvent { + // The ID of the network that has become the new default or NETID_UNSET if none. - public final int netId; + public int netId = NETID_UNSET; // The list of transport types of the new default network, for example TRANSPORT_WIFI, as // defined in NetworkCapabilities.java. - public final int[] transportTypes; + public int[] transportTypes = new int[0]; // The ID of the network that was the default before or NETID_UNSET if none. - public final int prevNetId; + public int prevNetId = NETID_UNSET; // Whether the previous network had IPv4/IPv6 connectivity. - public final boolean prevIPv4; - public final boolean prevIPv6; - - public DefaultNetworkEvent(int netId, int[] transportTypes, - int prevNetId, boolean prevIPv4, boolean prevIPv6) { - this.netId = netId; - this.transportTypes = transportTypes; - this.prevNetId = prevNetId; - this.prevIPv4 = prevIPv4; - this.prevIPv6 = prevIPv6; - } - - private DefaultNetworkEvent(Parcel in) { - this.netId = in.readInt(); - this.transportTypes = in.createIntArray(); - this.prevNetId = in.readInt(); - this.prevIPv4 = (in.readByte() > 0); - this.prevIPv6 = (in.readByte() > 0); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(netId); - out.writeIntArray(transportTypes); - out.writeInt(prevNetId); - out.writeByte(prevIPv4 ? (byte) 1 : (byte) 0); - out.writeByte(prevIPv6 ? (byte) 1 : (byte) 0); - } - - @Override - public int describeContents() { - return 0; - } + public boolean prevIPv4; + public boolean prevIPv6; @Override public String toString() { - String prevNetwork = String.valueOf(prevNetId); - String newNetwork = String.valueOf(netId); - if (prevNetId != 0) { - prevNetwork += ":" + ipSupport(); - } - if (netId != 0) { - newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes); - } - return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork); + String prevNetwork = String.valueOf(prevNetId); + String newNetwork = String.valueOf(netId); + if (prevNetId != 0) { + prevNetwork += ":" + ipSupport(); + } + if (netId != 0) { + newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes); + } + return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork); } private String ipSupport() { if (prevIPv4 && prevIPv6) { - return "DUAL"; + return "IPv4v6"; } if (prevIPv6) { return "IPv6"; @@ -92,15 +62,4 @@ public final class DefaultNetworkEvent implements Parcelable { } return "NONE"; } - - public static final Parcelable.Creator<DefaultNetworkEvent> CREATOR - = new Parcelable.Creator<DefaultNetworkEvent>() { - public DefaultNetworkEvent createFromParcel(Parcel in) { - return new DefaultNetworkEvent(in); - } - - public DefaultNetworkEvent[] newArray(int size) { - return new DefaultNetworkEvent[size]; - } - }; } diff --git a/android/net/util/VersionedBroadcastListener.java b/android/net/util/VersionedBroadcastListener.java new file mode 100644 index 00000000..107c4049 --- /dev/null +++ b/android/net/util/VersionedBroadcastListener.java @@ -0,0 +1,109 @@ +/* + * 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 android.net.util; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.util.Log; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + + +/** + * A utility class that runs the provided callback on the provided handler when + * intents matching the provided filter arrive. Intents received by a stale + * receiver are safely ignored. + * + * Calls to startListening() and stopListening() must happen on the same thread. + * + * @hide + */ +public class VersionedBroadcastListener { + private static final boolean DBG = false; + + public interface IntentCallback { + public void run(Intent intent); + } + + private final String mTag; + private final Context mContext; + private final Handler mHandler; + private final IntentFilter mFilter; + private final Consumer<Intent> mCallback; + private final AtomicInteger mGenerationNumber; + private BroadcastReceiver mReceiver; + + public VersionedBroadcastListener(String tag, Context ctx, Handler handler, + IntentFilter filter, Consumer<Intent> callback) { + mTag = tag; + mContext = ctx; + mHandler = handler; + mFilter = filter; + mCallback = callback; + mGenerationNumber = new AtomicInteger(0); + } + + public void startListening() { + if (DBG) Log.d(mTag, "startListening"); + if (mReceiver != null) return; + + mReceiver = new Receiver(mTag, mGenerationNumber, mCallback); + mContext.registerReceiver(mReceiver, mFilter, null, mHandler); + } + + public void stopListening() { + if (DBG) Log.d(mTag, "stopListening"); + if (mReceiver == null) return; + + mGenerationNumber.incrementAndGet(); + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + } + + private static class Receiver extends BroadcastReceiver { + public final String tag; + public final AtomicInteger atomicGenerationNumber; + public final Consumer<Intent> callback; + // Used to verify this receiver is still current. + public final int generationNumber; + + public Receiver( + String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) { + this.tag = tag; + this.atomicGenerationNumber = atomicGenerationNumber; + this.callback = callback; + generationNumber = atomicGenerationNumber.incrementAndGet(); + } + + @Override + public void onReceive(Context context, Intent intent) { + final int currentGenerationNumber = atomicGenerationNumber.get(); + + if (DBG) { + Log.d(tag, "receiver generationNumber=" + generationNumber + + ", current generationNumber=" + currentGenerationNumber); + } + if (generationNumber != currentGenerationNumber) return; + + callback.accept(intent); + } + } +} diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java index 649b0cef..c2959d5e 100644 --- a/android/net/wifi/WifiManager.java +++ b/android/net/wifi/WifiManager.java @@ -68,6 +68,7 @@ import java.util.concurrent.CountDownLatch; * leaks within the calling process. * <p> * It deals with several categories of items: + * </p> * <ul> * <li>The list of configured networks. The list can be viewed and updated, and * attributes of individual entries can be modified.</li> @@ -79,9 +80,11 @@ import java.util.concurrent.CountDownLatch; * <li>It defines the names of various Intent actions that are broadcast upon * any sort of change in Wi-Fi state. * </ul> + * <p> * This is the API to use when performing Wi-Fi specific operations. To perform * operations that pertain to network connectivity at an abstract level, use * {@link android.net.ConnectivityManager}. + * </p> */ @SystemService(Context.WIFI_SERVICE) public class WifiManager { @@ -1575,7 +1578,21 @@ public class WifiManager { * Request a scan for access points. Returns immediately. The availability * of the results is made known later by means of an asynchronous event sent * on completion of the scan. - * @return {@code true} if the operation succeeded, i.e., the scan was initiated + * <p> + * To initiate a Wi-Fi scan, declare the + * {@link android.Manifest.permission#CHANGE_WIFI_STATE} + * permission in the manifest, and perform these steps: + * </p> + * <ol style="1"> + * <li>Invoke the following method: + * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li> + * <li> + * Register a BroadcastReceiver to listen to + * {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li> + * <li>When a broadcast is received, call: + * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li> + * </ol> + * @return {@code true} if the operation succeeded, i.e., the scan was initiated. */ public boolean startScan() { return startScan(null); @@ -1877,32 +1894,6 @@ public class WifiManager { } /** - * This call is deprecated and removed. It is no longer used to - * start WiFi Tethering. Please use {@link ConnectivityManager#startTethering(int, boolean, - * ConnectivityManager#OnStartTetheringCallback)} if - * the caller has proper permissions. Callers can also use the LocalOnlyHotspot feature for a - * hotspot capable of communicating with co-located devices {@link - * WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}. - * - * @param wifiConfig SSID, security and channel details as - * part of WifiConfiguration - * @return {@code false} - * - * @hide - * @deprecated This API is nolonger supported. - * @removed - */ - @SystemApi - @Deprecated - @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) - public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { - String packageName = mContext.getOpPackageName(); - - Log.w(TAG, packageName + " attempted call to setWifiApEnabled: enabled = " + enabled); - return false; - } - - /** * Call allowing ConnectivityService to update WifiService with interface mode changes. * * The possible modes include: {@link IFACE_IP_MODE_TETHERED}, diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java index 8682c01e..a8bd9403 100644 --- a/android/os/BatteryStats.java +++ b/android/os/BatteryStats.java @@ -1986,7 +1986,7 @@ public abstract class BatteryStats implements Parcelable { public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which); /** - * Returns the number of times that the devie has started idling. + * Returns the number of times that the device has started idling. * * {@hide} */ @@ -6453,7 +6453,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(); } } - if (!filtering || (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0) { + if (!filtering || (flags & DUMP_DAILY_ONLY) != 0) { pw.println("Daily stats:"); pw.print(" Current start time: "); pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", diff --git a/android/os/Binder.java b/android/os/Binder.java index e9e695bb..2bfb0138 100644 --- a/android/os/Binder.java +++ b/android/os/Binder.java @@ -27,12 +27,14 @@ import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; import libcore.io.IoUtils; +import libcore.util.NativeAllocationRegistry; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Modifier; +import java.util.ArrayList; /** * Base class for a remotable object, the core part of a lightweight @@ -90,6 +92,20 @@ public class Binder implements IBinder { */ private static volatile TransactionTracker sTransactionTracker = null; + /** + * Guestimate of native memory associated with a Binder. + */ + private static final int NATIVE_ALLOCATION_SIZE = 500; + + private static native long getNativeFinalizer(); + + // Use a Holder to allow static initialization of Binder in the boot image, and + // possibly to avoid some initialization ordering issues. + private static class NoImagePreloadHolder { + public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + Binder.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE); + } + // Transaction tracking code. /** @@ -167,7 +183,7 @@ public class Binder implements IBinder { try { if (binder instanceof BinderProxy) { ((BinderProxy) binder).mWarnOnBlocking = false; - } else if (binder != null + } else if (binder != null && binder.getInterfaceDescriptor() != null && binder.queryLocalInterface(binder.getInterfaceDescriptor()) == null) { Log.w(TAG, "Unable to allow blocking on interface " + binder); } @@ -188,8 +204,11 @@ public class Binder implements IBinder { } } - /* mObject is used by native code, do not remove or rename */ - private long mObject; + /** + * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null. + */ + private final long mObject; + private IInterface mOwner; private String mDescriptor; @@ -360,7 +379,8 @@ public class Binder implements IBinder { * Default constructor initializes the object. */ public Binder() { - init(); + mObject = getNativeBBinderHolder(); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject); if (FIND_POTENTIAL_LEAKS) { final Class<? extends Binder> klass = getClass(); @@ -414,7 +434,7 @@ public class Binder implements IBinder { * descriptor. */ public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) { - if (mDescriptor.equals(descriptor)) { + if (mDescriptor != null && mDescriptor.equals(descriptor)) { return mOwner; } return null; @@ -643,14 +663,6 @@ public class Binder implements IBinder { return true; } - protected void finalize() throws Throwable { - try { - destroyBinder(); - } finally { - super.finalize(); - } - } - static void checkParcel(IBinder obj, int code, Parcel parcel, String msg) { if (CHECK_PARCEL_SIZE && parcel.dataSize() >= 800*1024) { // Trying to send > 800k, this is way too much @@ -674,8 +686,8 @@ public class Binder implements IBinder { } } - private native final void init(); - private native final void destroyBinder(); + private static native long getNativeBBinderHolder(); + private static native long getFinalizer(); // Entry point from android_util_Binder.cpp's onTransact private boolean execTransact(int code, long dataObj, long replyObj, @@ -736,11 +748,207 @@ public class Binder implements IBinder { */ final class BinderProxy implements IBinder { // See android_util_Binder.cpp for the native half of this. - // TODO: Consider using NativeAllocationRegistry instead of finalization. // Assume the process-wide default value when created volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking; + /* + * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies. + * We roll our own only because we need to lazily remove WeakReferences during accesses + * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable + * because we want weak values, not keys. + * Our hash table is never resized, but the number of entries is unlimited; + * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE. + * Not thread-safe. Client ensures there's a single access at a time. + */ + private static final class ProxyMap { + private static final int LOG_MAIN_INDEX_SIZE = 8; + private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE; + private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1; + + /** + * We next warn when we exceed this bucket size. + */ + private int mWarnBucketSize = 20; + + /** + * Increment mWarnBucketSize by WARN_INCREMENT each time we warn. + */ + private static final int WARN_INCREMENT = 10; + + /** + * Hash function tailored to native pointers. + * Returns a value < MAIN_INDEX_SIZE. + */ + private static int hash(long arg) { + return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK; + } + + /** + * Return the total number of pairs in the map. + */ + int size() { + int size = 0; + for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) { + if (a != null) { + size += a.size(); + } + } + return size; + } + + /** + * Remove ith entry from the hash bucket indicated by hash. + */ + private void remove(int hash, int index) { + Long[] keyArray = mMainIndexKeys[hash]; + ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash]; + int size = valueArray.size(); // KeyArray may have extra elements. + // Move last entry into empty slot, and truncate at end. + if (index != size - 1) { + keyArray[index] = keyArray[size - 1]; + valueArray.set(index, valueArray.get(size - 1)); + } + valueArray.remove(size - 1); + // Just leave key array entry; it's unused. We only trust the valueArray size. + } + + /** + * Look up the supplied key. If we have a non-cleared entry for it, return it. + */ + BinderProxy get(long key) { + int myHash = hash(key); + Long[] keyArray = mMainIndexKeys[myHash]; + if (keyArray == null) { + return null; + } + ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash]; + int bucketSize = valueArray.size(); + for (int i = 0; i < bucketSize; ++i) { + long foundKey = keyArray[i]; + if (key == foundKey) { + WeakReference<BinderProxy> wr = valueArray.get(i); + BinderProxy bp = wr.get(); + if (bp != null) { + return bp; + } else { + remove(myHash, i); + return null; + } + } + } + return null; + } + + private int mRandom; // A counter used to generate a "random" index. World's 2nd worst RNG. + + /** + * Add the key-value pair to the map. + * Requires that the indicated key is not already in the map. + */ + void set(long key, @NonNull BinderProxy value) { + int myHash = hash(key); + ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash]; + if (valueArray == null) { + valueArray = mMainIndexValues[myHash] = new ArrayList<>(); + mMainIndexKeys[myHash] = new Long[1]; + } + int size = valueArray.size(); + WeakReference<BinderProxy> newWr = new WeakReference<>(value); + // First look for a cleared reference. + // This ensures that ArrayList size is bounded by the maximum occupancy of + // that bucket. + for (int i = 0; i < size; ++i) { + if (valueArray.get(i).get() == null) { + valueArray.set(i, newWr); + Long[] keyArray = mMainIndexKeys[myHash]; + keyArray[i] = key; + if (i < size - 1) { + // "Randomly" check one of the remaining entries in [i+1, size), so that + // needlessly long buckets are eventually pruned. + int rnd = Math.floorMod(++mRandom, size - (i + 1)); + if (valueArray.get(i + 1 + rnd).get() == null) { + remove(myHash, i + 1 + rnd); + } + } + return; + } + } + valueArray.add(size, newWr); + Long[] keyArray = mMainIndexKeys[myHash]; + if (keyArray.length == size) { + // size >= 1, since we initially allocated one element + Long[] newArray = new Long[size + size / 2 + 2]; + System.arraycopy(keyArray, 0, newArray, 0, size); + newArray[size] = key; + mMainIndexKeys[myHash] = newArray; + } else { + keyArray[size] = key; + } + if (size >= mWarnBucketSize) { + Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size + + " total = " + size()); + mWarnBucketSize += WARN_INCREMENT; + } + } + + // Corresponding ArrayLists in the following two arrays always have the same size. + // They contain no empty entries. However WeakReferences in the values ArrayLists + // may have been cleared. + + // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) . + // The values ArrayList has the proper size(), the corresponding keys array + // is always at least the same size, but may be larger. + // If either a particular keys array, or the corresponding values ArrayList + // are null, then they both are. + private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][]; + private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues = + new ArrayList[MAIN_INDEX_SIZE]; + } + + private static ProxyMap sProxyMap = new ProxyMap(); + + /** + * Return a BinderProxy for IBinder. + * This method is thread-hostile! The (native) caller serializes getInstance() calls using + * gProxyLock. + * If we previously returned a BinderProxy bp for the same iBinder, and bp is still + * in use, then we return the same bp. + * + * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData. + * Takes ownership of nativeData iff <result>.mNativeData == nativeData. Caller will usually + * delete nativeData if that's not the case. + * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object. + */ + private static BinderProxy getInstance(long nativeData, long iBinder) { + BinderProxy result = sProxyMap.get(iBinder); + if (result == null) { + result = new BinderProxy(nativeData); + sProxyMap.set(iBinder, result); + } + return result; + } + + private BinderProxy(long nativeData) { + mNativeData = nativeData; + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeData); + } + + /** + * Guestimate of native memory associated with a BinderProxy. + * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector + * that points back to us. We guess high since it includes a GlobalRef, which + * may be in short supply. + */ + private static final int NATIVE_ALLOCATION_SIZE = 1000; + + // Use a Holder to allow static initialization of BinderProxy in the boot image, and + // to avoid some initialization ordering issues. + private static class NoImagePreloadHolder { + public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + BinderProxy.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE); + } + public native boolean pingBinder(); public native boolean isBinderAlive(); @@ -776,6 +984,7 @@ final class BinderProxy implements IBinder { } } + private static native long getNativeFinalizer(); public native String getInterfaceDescriptor() throws RemoteException; public native boolean transactNative(int code, Parcel data, Parcel reply, int flags) throws RemoteException; @@ -830,21 +1039,6 @@ final class BinderProxy implements IBinder { } } - BinderProxy() { - mSelf = new WeakReference(this); - } - - @Override - protected void finalize() throws Throwable { - try { - destroy(); - } finally { - super.finalize(); - } - } - - private native final void destroy(); - private static final void sendDeathNotice(DeathRecipient recipient) { if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient); try { @@ -856,19 +1050,9 @@ final class BinderProxy implements IBinder { } } - // This WeakReference to "this" is used only by native code to "attach" to the - // native IBinder object. - // Using WeakGlobalRefs instead currently appears unsafe, in that they can yield a - // non-null value after the BinderProxy is enqueued for finalization. - // Used only once immediately after construction. - // TODO: Consider making the extra native-to-java call to compute this on the fly. - final private WeakReference mSelf; - - // Native pointer to the wrapped native IBinder object. Counted as strong reference. - private long mObject; - - // Native pointer to native DeathRecipientList. Counted as strong reference. - // Basically owned by the JavaProxy object. Reference counted only because DeathRecipients - // hold a weak reference that can be temporarily promoted. - private long mOrgue; + /** + * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the + * native IBinder object, and a DeathRecipientList. + */ + private final long mNativeData; } diff --git a/android/os/IServiceManager.java b/android/os/IServiceManager.java index 87c65ecc..2176a785 100644 --- a/android/os/IServiceManager.java +++ b/android/os/IServiceManager.java @@ -45,13 +45,13 @@ public interface IServiceManager extends IInterface * Place a new @a service called @a name into the service * manager. */ - void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority) - throws RemoteException; + void addService(String name, IBinder service, boolean allowIsolated, int dumpFlags) + throws RemoteException; /** * Return a list of all currently running services. */ - String[] listServices(int dumpPriority) throws RemoteException; + String[] listServices(int dumpFlags) throws RemoteException; /** * Assign a permission controller to the service manager. After set, this @@ -72,9 +72,13 @@ public interface IServiceManager extends IInterface /* * Must update values in IServiceManager.h */ - int DUMP_PRIORITY_CRITICAL = 1 << 0; - int DUMP_PRIORITY_HIGH = 1 << 1; - int DUMP_PRIORITY_NORMAL = 1 << 2; - int DUMP_PRIORITY_ALL = DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_HIGH - | DUMP_PRIORITY_NORMAL; + /* Allows services to dump sections according to priorities. */ + int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0; + int DUMP_FLAG_PRIORITY_HIGH = 1 << 1; + int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2; + int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH + | DUMP_FLAG_PRIORITY_NORMAL; + /* Allows services to dump sections in protobuf format. */ + int DUMP_FLAG_PROTO = 1 << 3; + } diff --git a/android/os/Parcel.java b/android/os/Parcel.java index 857e8a60..c2cf3967 100644 --- a/android/os/Parcel.java +++ b/android/os/Parcel.java @@ -561,6 +561,22 @@ public final class Parcel { mClassCookies = from.mClassCookies; } + /** @hide */ + public Map<Class, Object> copyClassCookies() { + return new ArrayMap<>(mClassCookies); + } + + /** @hide */ + public void putClassCookies(Map<Class, Object> cookies) { + if (cookies == null) { + return; + } + if (mClassCookies == null) { + mClassCookies = new ArrayMap<>(); + } + mClassCookies.putAll(cookies); + } + /** * Report whether the parcel contains any marshalled file descriptors. */ diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java index 34c78455..42ec315c 100644 --- a/android/os/ServiceManager.java +++ b/android/os/ServiceManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * 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. @@ -16,9 +16,29 @@ package android.os; +import android.util.Log; + +import com.android.internal.os.BinderInternal; + +import java.util.HashMap; import java.util.Map; +/** @hide */ public final class ServiceManager { + private static final String TAG = "ServiceManager"; + private static IServiceManager sServiceManager; + private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>(); + + private static IServiceManager getIServiceManager() { + if (sServiceManager != null) { + return sServiceManager; + } + + // Find the service manager + sServiceManager = ServiceManagerNative + .asInterface(Binder.allowBlocking(BinderInternal.getContextObject())); + return sServiceManager; + } /** * Returns a reference to a service with the given name. @@ -27,14 +47,32 @@ public final class ServiceManager { * @return a reference to the service, or <code>null</code> if the service doesn't exist */ public static IBinder getService(String name) { + try { + IBinder service = sCache.get(name); + if (service != null) { + return service; + } else { + return Binder.allowBlocking(getIServiceManager().getService(name)); + } + } catch (RemoteException e) { + Log.e(TAG, "error in getService", e); + } return null; } /** - * Is not supposed to return null, but that is fine for layoutlib. + * Returns a reference to a service with the given name, or throws + * {@link NullPointerException} if none is found. + * + * @hide */ public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException { - throw new ServiceNotFoundException(name); + final IBinder binder = getService(name); + if (binder != null) { + return binder; + } else { + throw new ServiceNotFoundException(name); + } } /** @@ -45,7 +83,39 @@ public final class ServiceManager { * @param service the service object */ public static void addService(String name, IBinder service) { - // pass + addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL); + } + + /** + * Place a new @a service called @a name into the service + * manager. + * + * @param name the name of the new service + * @param service the service object + * @param allowIsolated set to true to allow isolated sandboxed processes + * to access this service + */ + public static void addService(String name, IBinder service, boolean allowIsolated) { + addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL); + } + + /** + * Place a new @a service called @a name into the service + * manager. + * + * @param name the name of the new service + * @param service the service object + * @param allowIsolated set to true to allow isolated sandboxed processes + * @param dumpPriority supported dump priority levels as a bitmask + * to access this service + */ + public static void addService(String name, IBinder service, boolean allowIsolated, + int dumpPriority) { + try { + getIServiceManager().addService(name, service, allowIsolated, dumpPriority); + } catch (RemoteException e) { + Log.e(TAG, "error in addService", e); + } } /** @@ -53,7 +123,17 @@ public final class ServiceManager { * service manager. Non-blocking. */ public static IBinder checkService(String name) { - return null; + try { + IBinder service = sCache.get(name); + if (service != null) { + return service; + } else { + return Binder.allowBlocking(getIServiceManager().checkService(name)); + } + } catch (RemoteException e) { + Log.e(TAG, "error in checkService", e); + return null; + } } /** @@ -62,9 +142,12 @@ public final class ServiceManager { * case of an exception */ public static String[] listServices() { - // actual implementation returns null sometimes, so it's ok - // to return null instead of an empty list. - return null; + try { + return getIServiceManager().listServices(IServiceManager.DUMP_FLAG_PRIORITY_ALL); + } catch (RemoteException e) { + Log.e(TAG, "error in listServices", e); + return null; + } } /** @@ -76,7 +159,10 @@ public final class ServiceManager { * @hide */ public static void initServiceCache(Map<String, IBinder> cache) { - // pass + if (sCache.size() != 0) { + throw new IllegalStateException("setServiceCache may only be called once"); + } + sCache.putAll(cache); } /** @@ -87,7 +173,6 @@ public final class ServiceManager { * @hide */ public static class ServiceNotFoundException extends Exception { - // identical to the original implementation public ServiceNotFoundException(String name) { super("No service published for: " + name); } diff --git a/android/os/ShellCommand.java b/android/os/ShellCommand.java index e4a12e84..6223235e 100644 --- a/android/os/ShellCommand.java +++ b/android/os/ShellCommand.java @@ -17,6 +17,7 @@ package android.os; import android.util.Slog; + import com.android.internal.util.FastPrintWriter; import java.io.BufferedInputStream; @@ -118,13 +119,33 @@ public abstract class ShellCommand { mErrPrintWriter.flush(); } if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget); - mResultReceiver.send(res, null); + if (mResultReceiver != null) { + mResultReceiver.send(res, null); + } } if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget); return res; } /** + * Adopt the ResultReceiver that was given to this shell command from it, taking + * it over. Primarily used to dispatch to another shell command. Once called, + * this shell command will no longer return its own result when done. + */ + public ResultReceiver adoptResultReceiver() { + ResultReceiver rr = mResultReceiver; + mResultReceiver = null; + return rr; + } + + /** + * Return the raw FileDescriptor for the output stream. + */ + public FileDescriptor getOutFileDescriptor() { + return mOut; + } + + /** * Return direct raw access (not buffered) to the command's output data stream. */ public OutputStream getRawOutputStream() { @@ -145,6 +166,13 @@ public abstract class ShellCommand { } /** + * Return the raw FileDescriptor for the error stream. + */ + public FileDescriptor getErrFileDescriptor() { + return mErr; + } + + /** * Return direct raw access (not buffered) to the command's error output data stream. */ public OutputStream getRawErrorStream() { @@ -168,6 +196,13 @@ public abstract class ShellCommand { } /** + * Return the raw FileDescriptor for the input stream. + */ + public FileDescriptor getInFileDescriptor() { + return mIn; + } + + /** * Return direct raw access (not buffered) to the command's input data stream. */ public InputStream getRawInputStream() { diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java index 826ec1eb..ee3e5bc9 100644 --- a/android/os/StrictMode.java +++ b/android/os/StrictMode.java @@ -20,7 +20,6 @@ import android.annotation.Nullable; import android.annotation.TestApi; import android.app.ActivityManager; import android.app.ActivityThread; -import android.app.ApplicationErrorReport; import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -35,6 +34,7 @@ import android.util.Singleton; import android.util.Slog; import android.view.IWindowManager; +import com.android.internal.annotations.GuardedBy; import com.android.internal.os.RuntimeInit; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.HexDump; @@ -48,8 +48,10 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Deque; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -352,8 +354,8 @@ public final class StrictMode { } else { msg = "StrictMode policy violation:"; } - if (info.crashInfo != null) { - Log.d(TAG, msg + " " + info.crashInfo.stackTrace); + if (info.hasStackTrace()) { + Log.d(TAG, msg + " " + info.getStackTrace()); } else { Log.d(TAG, msg + " missing stack trace!"); } @@ -1247,28 +1249,6 @@ public final class StrictMode { } } - /** Like parsePolicyFromMessage(), but returns the violation. */ - private static int parseViolationFromMessage(String message) { - if (message == null) { - return 0; - } - int violationIndex = message.indexOf("violation="); - if (violationIndex == -1) { - return 0; - } - int numberStartIndex = violationIndex + "violation=".length(); - int numberEndIndex = message.indexOf(' ', numberStartIndex); - if (numberEndIndex == -1) { - numberEndIndex = message.length(); - } - String violationString = message.substring(numberStartIndex, numberEndIndex); - try { - return Integer.parseInt(violationString); - } catch (NumberFormatException e) { - return 0; - } - } - private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed = new ThreadLocal<ArrayList<ViolationInfo>>() { @Override @@ -1516,7 +1496,7 @@ public final class StrictMode { // to people who push/pop temporary policy in regions of code, // hence the policy being passed around. void handleViolation(final ViolationInfo info) { - if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) { + if (info == null || !info.hasStackTrace()) { Log.wtf(TAG, "unexpected null stacktrace"); return; } @@ -1530,7 +1510,7 @@ public final class StrictMode { gatheredViolations.set(violations); } for (ViolationInfo previous : violations) { - if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) { + if (info.getStackTrace().equals(previous.getStackTrace())) { // Duplicate. Don't log. return; } @@ -1576,8 +1556,7 @@ public final class StrictMode { } if (violationMaskSubset != 0) { - int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage); - violationMaskSubset |= violationBit; + violationMaskSubset |= info.getViolationBit(); final int savedPolicyMask = getThreadPolicyMask(); final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX; @@ -1622,8 +1601,7 @@ public final class StrictMode { } private static void executeDeathPenalty(ViolationInfo info) { - int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage); - throw new StrictModeViolation(info.policy, violationBit, null); + throw new StrictModeViolation(info.policy, info.getViolationBit(), null); } /** @@ -1670,7 +1648,7 @@ public final class StrictMode { private static class AndroidCloseGuardReporter implements CloseGuard.Reporter { public void report(String message, Throwable allocationSite) { - onVmPolicyViolation(message, allocationSite); + onVmPolicyViolation(allocationSite); } } @@ -1709,7 +1687,7 @@ public final class StrictMode { long instances = instanceCounts[i]; if (instances > limit) { Throwable tr = new InstanceCountViolation(klass, instances, limit); - onVmPolicyViolation(tr.getMessage(), tr); + onVmPolicyViolation(tr); } } } @@ -1833,22 +1811,24 @@ public final class StrictMode { /** @hide */ public static void onSqliteObjectLeaked(String message, Throwable originStack) { - onVmPolicyViolation(message, originStack); + Throwable t = new Throwable(message); + t.setStackTrace(originStack.getStackTrace()); + onVmPolicyViolation(t); } /** @hide */ public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) { - onVmPolicyViolation(null, originStack); + onVmPolicyViolation(originStack); } /** @hide */ public static void onIntentReceiverLeaked(Throwable originStack) { - onVmPolicyViolation(null, originStack); + onVmPolicyViolation(originStack); } /** @hide */ public static void onServiceConnectionLeaked(Throwable originStack) { - onVmPolicyViolation(null, originStack); + onVmPolicyViolation(originStack); } /** @hide */ @@ -1857,7 +1837,7 @@ public final class StrictMode { if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) { throw new FileUriExposedException(message); } else { - onVmPolicyViolation(null, new Throwable(message)); + onVmPolicyViolation(new Throwable(message)); } } @@ -1869,7 +1849,7 @@ public final class StrictMode { + location + " without permission grant flags; did you forget" + " FLAG_GRANT_READ_URI_PERMISSION?"; - onVmPolicyViolation(null, new Throwable(message)); + onVmPolicyViolation(new Throwable(message)); } /** @hide */ @@ -1899,10 +1879,9 @@ public final class StrictMode { } catch (UnknownHostException ignored) { } } - + msg += HexDump.dumpHexString(firstPacket).trim() + " "; final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0; - onVmPolicyViolation( - HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), forceDeath); + onVmPolicyViolation(new Throwable(msg), forceDeath); } /** @hide */ @@ -1912,24 +1891,23 @@ public final class StrictMode { /** @hide */ public static void onUntaggedSocket() { - onVmPolicyViolation(null, new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE)); + onVmPolicyViolation(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE)); } // Map from VM violation fingerprint to uptime millis. private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>(); /** @hide */ - public static void onVmPolicyViolation(String message, Throwable originStack) { - onVmPolicyViolation(message, originStack, false); + public static void onVmPolicyViolation(Throwable originStack) { + onVmPolicyViolation(originStack, false); } /** @hide */ - public static void onVmPolicyViolation( - String message, Throwable originStack, boolean forceDeath) { + public static void onVmPolicyViolation(Throwable originStack, boolean forceDeath) { final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0; final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath; final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0; - final ViolationInfo info = new ViolationInfo(message, originStack, sVmPolicy.mask); + final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask); // Erase stuff not relevant for process-wide violations info.numAnimationsRunning = 0; @@ -2027,21 +2005,14 @@ public final class StrictMode { * read back all the encoded violations. */ /* package */ static void readAndHandleBinderCallViolations(Parcel p) { - // Our own stack trace to append - StringWriter sw = new StringWriter(); - sw.append("# via Binder call with stack:\n"); - PrintWriter pw = new FastPrintWriter(sw, false, 256); - new LogStackTrace().printStackTrace(pw); - pw.flush(); - String ourStack = sw.toString(); - + LogStackTrace localCallSite = new LogStackTrace(); final int policyMask = getThreadPolicyMask(); final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0; final int size = p.readInt(); for (int i = 0; i < size; i++) { final ViolationInfo info = new ViolationInfo(p, !currentlyGathering); - info.crashInfo.appendStackTrace(ourStack); + info.addLocalStack(localCallSite); BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); if (policy instanceof AndroidBlockGuardPolicy) { ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info); @@ -2254,7 +2225,7 @@ public final class StrictMode { // StrictMode not enabled. return; } - ((AndroidBlockGuardPolicy) policy).onUnbufferedIO(); + policy.onUnbufferedIO(); } /** @hide */ @@ -2264,7 +2235,7 @@ public final class StrictMode { // StrictMode not enabled. return; } - ((AndroidBlockGuardPolicy) policy).onReadFromDisk(); + policy.onReadFromDisk(); } /** @hide */ @@ -2274,12 +2245,11 @@ public final class StrictMode { // StrictMode not enabled. return; } - ((AndroidBlockGuardPolicy) policy).onWriteToDisk(); + policy.onWriteToDisk(); } - // Guarded by StrictMode.class - private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = - new HashMap<Class, Integer>(); + @GuardedBy("StrictMode.class") + private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = new HashMap<>(); /** * Returns an object that is used to track instances of activites. The activity should store a @@ -2354,7 +2324,7 @@ public final class StrictMode { long instances = VMDebug.countInstancesOfClass(klass, false); if (instances > limit) { Throwable tr = new InstanceCountViolation(klass, instances, limit); - onVmPolicyViolation(tr.getMessage(), tr); + onVmPolicyViolation(tr); } } @@ -2366,10 +2336,15 @@ public final class StrictMode { */ @TestApi public static final class ViolationInfo implements Parcelable { - public final String message; + /** Stack and violation details. */ + @Nullable private final Throwable mThrowable; + + private final Deque<Throwable> mBinderStack = new ArrayDeque<>(); - /** Stack and other stuff info. */ - @Nullable public final ApplicationErrorReport.CrashInfo crashInfo; + /** Memoized stack trace of full violation. */ + @Nullable private String mStackTrace; + /** Memoized violation bit. */ + private int mViolationBit; /** The strict mode policy mask at the time of violation. */ public final int policy; @@ -2404,19 +2379,13 @@ public final class StrictMode { /** Create an uninitialized instance of ViolationInfo */ public ViolationInfo() { - message = null; - crashInfo = null; + mThrowable = null; policy = 0; } - public ViolationInfo(Throwable tr, int policy) { - this(null, tr, policy); - } - /** Create an instance of ViolationInfo initialized from an exception. */ - public ViolationInfo(String message, Throwable tr, int policy) { - this.message = message; - crashInfo = new ApplicationErrorReport.CrashInfo(tr); + public ViolationInfo(Throwable tr, int policy) { + this.mThrowable = tr; violationUptimeMillis = SystemClock.uptimeMillis(); this.policy = policy; this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount(); @@ -2446,11 +2415,91 @@ public final class StrictMode { } } + /** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */ + public String getStackTrace() { + if (mThrowable != null && mStackTrace == null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new FastPrintWriter(sw, false, 256); + mThrowable.printStackTrace(pw); + for (Throwable t : mBinderStack) { + pw.append("# via Binder call with stack:\n"); + t.printStackTrace(pw); + } + pw.flush(); + pw.close(); + mStackTrace = sw.toString(); + } + return mStackTrace; + } + + /** + * Optional message describing this violation. + * + * @hide + */ + @TestApi + public String getViolationDetails() { + if (mThrowable != null) { + return mThrowable.getMessage(); + } else { + return ""; + } + } + + /** + * If this violation has a useful stack trace. + * + * @hide + */ + public boolean hasStackTrace() { + return mThrowable != null; + } + + /** + * Add a {@link Throwable} from the current process that caused the underlying violation. + * + * @hide + */ + void addLocalStack(Throwable t) { + mBinderStack.addFirst(t); + } + + /** + * Retrieve the type of StrictMode violation. + * + * @hide + */ + int getViolationBit() { + if (mThrowable == null || mThrowable.getMessage() == null) { + return 0; + } + if (mViolationBit != 0) { + return mViolationBit; + } + String message = mThrowable.getMessage(); + int violationIndex = message.indexOf("violation="); + if (violationIndex == -1) { + return 0; + } + int numberStartIndex = violationIndex + "violation=".length(); + int numberEndIndex = message.indexOf(' ', numberStartIndex); + if (numberEndIndex == -1) { + numberEndIndex = message.length(); + } + String violationString = message.substring(numberStartIndex, numberEndIndex); + try { + mViolationBit = Integer.parseInt(violationString); + return mViolationBit; + } catch (NumberFormatException e) { + return 0; + } + } + @Override public int hashCode() { int result = 17; - if (crashInfo != null) { - result = 37 * result + crashInfo.stackTrace.hashCode(); + if (mThrowable != null) { + result = 37 * result + mThrowable.hashCode(); } if (numAnimationsRunning != 0) { result *= 37; @@ -2478,11 +2527,10 @@ public final class StrictMode { * should be removed. */ public ViolationInfo(Parcel in, boolean unsetGatheringBit) { - message = in.readString(); - if (in.readInt() != 0) { - crashInfo = new ApplicationErrorReport.CrashInfo(in); - } else { - crashInfo = null; + mThrowable = (Throwable) in.readSerializable(); + int binderStackSize = in.readInt(); + for (int i = 0; i < binderStackSize; i++) { + mBinderStack.add((Throwable) in.readSerializable()); } int rawPolicy = in.readInt(); if (unsetGatheringBit) { @@ -2502,12 +2550,10 @@ public final class StrictMode { /** Save a ViolationInfo instance to a parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeString(message); - if (crashInfo != null) { - dest.writeInt(1); - crashInfo.writeToParcel(dest, flags); - } else { - dest.writeInt(0); + dest.writeSerializable(mThrowable); + dest.writeInt(mBinderStack.size()); + for (Throwable t : mBinderStack) { + dest.writeSerializable(t); } int start = dest.dataPosition(); dest.writeInt(policy); @@ -2542,8 +2588,8 @@ public final class StrictMode { /** Dump a ViolationInfo instance to a Printer. */ public void dump(Printer pw, String prefix) { - if (crashInfo != null) { - crashInfo.dump(pw, prefix); + if (mThrowable != null) { + pw.println(prefix + "stackTrace: " + getStackTrace()); } pw.println(prefix + "policy: " + policy); if (durationMillis != -1) { diff --git a/android/os/UserManager.java b/android/os/UserManager.java index 8c688713..c54b72d4 100644 --- a/android/os/UserManager.java +++ b/android/os/UserManager.java @@ -319,6 +319,23 @@ public class UserManager { public static final String DISALLOW_CONFIG_VPN = "no_config_vpn"; /** + * Specifies if date, time and timezone configuring is disallowed. + * + * <p>When restriction is set by device owners, it applies globally - i.e., it disables date, + * time and timezone setting on the entire device and all users will be affected. When it's set + * by profile owners, it's only applied to the managed user. + * <p>The default value is <code>false</code>. + * + * <p>This user restriction has no effect on managed profiles. + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time"; + + /** * Specifies if a user is disallowed from configuring Tethering * & portable hotspots. This can only be set by device owners and profile owners on the * primary user. The default value is <code>false</code>. diff --git a/android/provider/Settings.java b/android/provider/Settings.java index a27df3a7..62f4bf58 100644 --- a/android/provider/Settings.java +++ b/android/provider/Settings.java @@ -3096,6 +3096,12 @@ public final class Settings { private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator; /** + * The display color mode. + * @hide + */ + public static final String DISPLAY_COLOR_MODE = "display_color_mode"; + + /** * The amount of time in milliseconds before the device goes to sleep or begins * to dream after a period of inactivity. This value is also known as the * user activity timeout period since the screen isn't necessarily turned off diff --git a/android/security/NetworkSecurityPolicy.java b/android/security/NetworkSecurityPolicy.java index 812c956f..0c4eedab 100644 --- a/android/security/NetworkSecurityPolicy.java +++ b/android/security/NetworkSecurityPolicy.java @@ -16,7 +16,6 @@ package android.security; -import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; import android.security.net.config.ApplicationConfig; @@ -63,7 +62,8 @@ public class NetworkSecurityPolicy { * traffic from applications is handled by higher-level network stacks/components which can * honor this aspect of the policy. * - * <p>NOTE: {@link android.webkit.WebView} does not honor this flag. + * <p>NOTE: {@link android.webkit.WebView} honors this flag for applications targeting API level + * 26 and up. */ public boolean isCleartextTrafficPermitted() { return libcore.net.NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(); diff --git a/android/security/net/config/NetworkSecurityConfig.java b/android/security/net/config/NetworkSecurityConfig.java index b9e55054..52f48ef8 100644 --- a/android/security/net/config/NetworkSecurityConfig.java +++ b/android/security/net/config/NetworkSecurityConfig.java @@ -164,7 +164,8 @@ public final class NetworkSecurityConfig { * <p> * The default configuration has the following properties: * <ol> - * <li>Cleartext traffic is permitted for non-ephemeral apps.</li> + * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic + * is allowed by default.</li> * <li>Cleartext traffic is not permitted for ephemeral apps.</li> * <li>HSTS is not enforced.</li> * <li>No certificate pinning is used.</li> @@ -183,7 +184,8 @@ public final class NetworkSecurityConfig { // System certificate store, does not bypass static pins. .addCertificatesEntryRef( new CertificatesEntryRef(SystemCertificateSource.getInstance(), false)); - final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2; + final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P + && info.targetSandboxVersion < 2; builder.setCleartextTrafficPermitted(cleartextTrafficPermitted); // Applications targeting N and above must opt in into trusting the user added certificate // store. diff --git a/android/service/autofill/CustomDescription.java b/android/service/autofill/CustomDescription.java index fd30857d..b8e8b19f 100644 --- a/android/service/autofill/CustomDescription.java +++ b/android/service/autofill/CustomDescription.java @@ -32,7 +32,7 @@ import com.android.internal.util.Preconditions; import java.util.ArrayList; /** - * Defines a custom description for the Save UI affordance. + * Defines a custom description for the autofill save UI. * * <p>This is useful when the autofill service needs to show a detailed view of what would be saved; * for example, when the screen contains a credit card, it could display a logo of the credit card @@ -131,7 +131,7 @@ public final class CustomDescription implements Parcelable { * <p><b>Note:</b> If any child view of presentation triggers a * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent) pending intent * on click}, such {@link PendingIntent} must follow the restrictions below, otherwise - * it might not be triggered or the Save affordance might not be shown when its activity + * it might not be triggered or the autofill save UI might not be shown when its activity * is finished: * <ul> * <li>It cannot be created with the {@link PendingIntent#FLAG_IMMUTABLE} flag. diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java index d2033fa9..2f6342af 100644 --- a/android/service/autofill/FillResponse.java +++ b/android/service/autofill/FillResponse.java @@ -31,6 +31,8 @@ import android.os.Parcelable; import android.view.autofill.AutofillId; import android.widget.RemoteViews; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -51,9 +53,16 @@ public final class FillResponse implements Parcelable { */ public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1; + /** + * Used in conjunction to {@link FillResponse.Builder#disableAutofill(long)} to disable autofill + * only for the activiy associated with the {@link FillResponse}, instead of the whole app. + */ + public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2; + /** @hide */ @IntDef(flag = true, value = { - FLAG_TRACK_CONTEXT_COMMITED + FLAG_TRACK_CONTEXT_COMMITED, + FLAG_DISABLE_ACTIVITY_ONLY }) @Retention(RetentionPolicy.SOURCE) @interface FillResponseFlags {} @@ -65,6 +74,7 @@ public final class FillResponse implements Parcelable { private final @Nullable IntentSender mAuthentication; private final @Nullable AutofillId[] mAuthenticationIds; private final @Nullable AutofillId[] mIgnoredIds; + private final long mDisableDuration; private final int mFlags; private int mRequestId; @@ -76,6 +86,7 @@ public final class FillResponse implements Parcelable { mAuthentication = builder.mAuthentication; mAuthenticationIds = builder.mAuthenticationIds; mIgnoredIds = builder.mIgnoredIds; + mDisableDuration = builder.mDisableDuration; mFlags = builder.mFlags; mRequestId = INVALID_REQUEST_ID; } @@ -116,6 +127,11 @@ public final class FillResponse implements Parcelable { } /** @hide */ + public long getDisableDuration() { + return mDisableDuration; + } + + /** @hide */ public int getFlags() { return mFlags; } @@ -150,6 +166,7 @@ public final class FillResponse implements Parcelable { private IntentSender mAuthentication; private AutofillId[] mAuthenticationIds; private AutofillId[] mIgnoredIds; + private long mDisableDuration; private int mFlags; private boolean mDestroyed; @@ -187,23 +204,25 @@ public final class FillResponse implements Parcelable { * which is used to visualize visualize the response for triggering the authentication * flow. * - * <p></><strong>Note:</strong> Do not make the provided pending intent + * <p><b>Note:</b> Do not make the provided pending intent * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the * platform needs to fill in the authentication arguments. * * @param authentication Intent to an activity with your authentication flow. * @param presentation The presentation to visualize the response. - * @param ids id of Views that when focused will display the authentication UI affordance. + * @param ids id of Views that when focused will display the authentication UI. * * @return This builder. * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if - * neither {@code authentication} nor {@code presentation} is non-{@code null}. + * both {@code authentication} and {@code presentation} are {@code null}, or if + * both {@code authentication} and {@code presentation} are non-{@code null} * * @see android.app.PendingIntent#getIntentSender() */ public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { throwIfDestroyed(); + throwIfDisableAutofillCalled(); if (ids == null || ids.length == 0) { throw new IllegalArgumentException("ids cannot be null or empry"); } @@ -226,6 +245,7 @@ public final class FillResponse implements Parcelable { * text field representing the result of a Captcha challenge. */ public Builder setIgnoredIds(AutofillId...ids) { + throwIfDestroyed(); mIgnoredIds = ids; return this; } @@ -246,6 +266,7 @@ public final class FillResponse implements Parcelable { */ public @NonNull Builder addDataset(@Nullable Dataset dataset) { throwIfDestroyed(); + throwIfDisableAutofillCalled(); if (dataset == null) { return this; } @@ -265,6 +286,7 @@ public final class FillResponse implements Parcelable { */ public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) { throwIfDestroyed(); + throwIfDisableAutofillCalled(); mSaveInfo = saveInfo; return this; } @@ -295,30 +317,82 @@ public final class FillResponse implements Parcelable { /** * Sets flags changing the response behavior. * - * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}. + * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and + * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}. * * @return This builder. */ public Builder setFlags(@FillResponseFlags int flags) { throwIfDestroyed(); - mFlags = flags; + mFlags = Preconditions.checkFlagsArgument(flags, + FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY); + return this; + } + + /** + * Disables autofill for the app or activity. + * + * <p>This method is useful to optimize performance in cases where the service knows it + * can not autofill an app—for example, when the service has a list of "blacklisted" + * apps such as office suites. + * + * <p>By default, it disables autofill for all activities in the app, unless the response is + * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}. + * + * <p>Autofill for the app or activity is automatically re-enabled after any of the + * following conditions: + * + * <ol> + * <li>{@code duration} milliseconds have passed. + * <li>The autofill service for the user has changed. + * <li>The device has rebooted. + * </ol> + * + * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain + * disabled for autofill until they finish and restart. + * + * @param duration duration to disable autofill, in milliseconds. + * + * @return this builder + * + * @throws IllegalArgumentException if {@code duration} is not a positive number. + * @throws IllegalStateException if either {@link #addDataset(Dataset)}, + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or + * {@link #setSaveInfo(SaveInfo)} was already called. + */ + public Builder disableAutofill(long duration) { + throwIfDestroyed(); + if (duration <= 0) { + throw new IllegalArgumentException("duration must be greater than 0"); + } + if (mAuthentication != null || mDatasets != null || mSaveInfo != null) { + throw new IllegalStateException("disableAutofill() must be the only method called"); + } + + mDisableDuration = duration; return this; } /** * Builds a new {@link FillResponse} instance. * - * <p>You must provide at least one dataset or some savable ids or an authentication with a - * presentation view. + * @throws IllegalStateException if any of the following conditions occur: + * <ol> + * <li>{@link #build()} was already called. + * <li>No call was made to {@link #addDataset(Dataset)}, + * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, + * {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}. + * </ol> * * @return A built response. */ public FillResponse build() { throwIfDestroyed(); - if (mAuthentication == null && mDatasets == null && mSaveInfo == null) { - throw new IllegalArgumentException("need to provide at least one DataSet or a " - + "SaveInfo or an authentication with a presentation"); + if (mAuthentication == null && mDatasets == null && mSaveInfo == null + && mDisableDuration == 0) { + throw new IllegalStateException("need to provide at least one DataSet or a " + + "SaveInfo or an authentication with a presentation or disable autofill"); } mDestroyed = true; return new FillResponse(this); @@ -329,6 +403,12 @@ public final class FillResponse implements Parcelable { throw new IllegalStateException("Already called #build()"); } } + + private void throwIfDisableAutofillCalled() { + if (mDisableDuration > 0) { + throw new IllegalStateException("Already called #disableAutofill()"); + } + } } ///////////////////////////////////// @@ -348,6 +428,7 @@ public final class FillResponse implements Parcelable { .append(", hasAuthentication=").append(mAuthentication != null) .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)) .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds)) + .append(", disableDuration=").append(mDisableDuration) .append(", flags=").append(mFlags) .append("]") .toString(); @@ -371,6 +452,7 @@ public final class FillResponse implements Parcelable { parcel.writeParcelable(mAuthentication, flags); parcel.writeParcelable(mPresentation, flags); parcel.writeParcelableArray(mIgnoredIds, flags); + parcel.writeLong(mDisableDuration); parcel.writeInt(mFlags); parcel.writeInt(mRequestId); } @@ -402,6 +484,10 @@ public final class FillResponse implements Parcelable { } builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class)); + final long disableDuration = parcel.readLong(); + if (disableDuration > 0) { + builder.disableAutofill(disableDuration); + } builder.setFlags(parcel.readInt()); final FillResponse response = builder.build(); diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java index fde2416f..9a1dcbb2 100644 --- a/android/service/autofill/SaveInfo.java +++ b/android/service/autofill/SaveInfo.java @@ -535,14 +535,15 @@ public final class SaveInfo implements Parcelable { * 16 digits, or 15 digits starting with 108: * * <pre class="prettyprint"> - * import android.service.autofill.Validators; + * import static android.service.autofill.Validators.and; + * import static android.service.autofill.Validators.or; * * Validator validator = * and( * new LuhnChecksumValidator(ccNumberId), * or( - * new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$")), - * new RegexValidator(ccNumberId, Pattern.compile(""^108\\d{12}$")) + * new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")), + * new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$")) * ) * ); * </pre> @@ -562,14 +563,14 @@ public final class SaveInfo implements Parcelable { * 4 digits on each field: * * <pre class="prettyprint"> - * import android.service.autofill.Validators; + * import static android.service.autofill.Validators.and; * * Validator validator = * and( - * new RegexValidator(ccNumberId1, Pattern.compile(""^\\d{4}$")), - * new RegexValidator(ccNumberId2, Pattern.compile(""^\\d{4}$")), - * new RegexValidator(ccNumberId3, Pattern.compile(""^\\d{4}$")), - * new RegexValidator(ccNumberId4, Pattern.compile(""^\\d{4}$")) + * new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")), + * new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")), + * new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")), + * new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$")) * ); * </pre> * diff --git a/android/service/autofill/Transformation.java b/android/service/autofill/Transformation.java index 4cef261d..aa8bc9b9 100644 --- a/android/service/autofill/Transformation.java +++ b/android/service/autofill/Transformation.java @@ -19,7 +19,7 @@ package android.service.autofill; * Helper class used to change a child view of a {@link android.widget.RemoteViews presentation * template} at runtime, using the values of fields contained in the screen. * - * <p>Typically used by {@link CustomDescription} to provide a customized Save UI affordance. + * <p>Typically used by {@link CustomDescription} to provide a customized autofill save UI. */ public interface Transformation { } diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java index edc32415..2684c58a 100644 --- a/android/support/car/widget/CarRecyclerView.java +++ b/android/support/car/widget/CarRecyclerView.java @@ -18,8 +18,6 @@ package android.support.car.widget; import android.content.Context; import android.graphics.Canvas; -import android.os.Parcel; -import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; @@ -27,9 +25,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - /** * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate. * @@ -37,11 +32,7 @@ import java.lang.reflect.InvocationTargetException; * #setFadeLastItem(boolean)}. */ public class CarRecyclerView extends RecyclerView { - private static final String PARCEL_CLASS = "android.os.Parcel"; - private static final String SAVED_STATE_CLASS = - "android.support.v7.widget.RecyclerView.SavedState"; private boolean mFadeLastItem; - private Constructor<?> mSavedStateConstructor; /** * If the user releases the list with a velocity of 0, {@link #fling(int, int)} will not be * called. However, we want to make sure that the list still snaps to the next page when this @@ -64,30 +55,6 @@ public class CarRecyclerView extends RecyclerView { } @Override - protected void onRestoreInstanceState(Parcelable state) { - if (state.getClass().getClassLoader() != getClass().getClassLoader()) { - if (mSavedStateConstructor == null) { - mSavedStateConstructor = getSavedStateConstructor(); - } - // Class loader mismatch, recreate from parcel. - Parcel obtain = Parcel.obtain(); - state.writeToParcel(obtain, 0); - try { - Parcelable newState = (Parcelable) mSavedStateConstructor.newInstance(obtain); - super.onRestoreInstanceState(newState); - } catch (InstantiationException - | IllegalAccessException - | IllegalArgumentException - | InvocationTargetException e) { - // Fail loudy here. - throw new RuntimeException(e); - } - } else { - super.onRestoreInstanceState(state); - } - } - - @Override public boolean fling(int velocityX, int velocityY) { mWasFlingCalledForGesture = true; return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY); @@ -158,35 +125,6 @@ public class CarRecyclerView extends RecyclerView { smoothScrollToPosition(pageDownPosition); } - /** Sets {@link #mSavedStateConstructor} to private SavedState constructor. */ - private Constructor<?> getSavedStateConstructor() { - Class<?> savedStateClass = null; - // Find package private subclass RecyclerView$SavedState. - for (Class<?> c : RecyclerView.class.getDeclaredClasses()) { - if (c.getCanonicalName().equals(SAVED_STATE_CLASS)) { - savedStateClass = c; - break; - } - } - if (savedStateClass == null) { - throw new RuntimeException("RecyclerView$SavedState not found!"); - } - // Find constructor that takes a {@link Parcel}. - for (Constructor<?> c : savedStateClass.getDeclaredConstructors()) { - Class<?>[] parameterTypes = c.getParameterTypes(); - if (parameterTypes.length == 1 - && parameterTypes[0].getCanonicalName().equals(PARCEL_CLASS)) { - mSavedStateConstructor = c; - mSavedStateConstructor.setAccessible(true); - break; - } - } - if (mSavedStateConstructor == null) { - throw new RuntimeException("RecyclerView$SavedState constructor not found!"); - } - return mSavedStateConstructor; - } - /** * Fades child by alpha. If child is a {@link ViewGroup} then it will recursively fade its * children instead. diff --git a/android/support/design/widget/CoordinatorLayout.java b/android/support/design/widget/CoordinatorLayout.java index 94de9b86..477a8d62 100644 --- a/android/support/design/widget/CoordinatorLayout.java +++ b/android/support/design/widget/CoordinatorLayout.java @@ -610,8 +610,8 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen } Constructor<Behavior> c = constructors.get(fullName); if (c == null) { - final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true, - context.getClassLoader()); + final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader() + .loadClass(fullName); c = clazz.getConstructor(CONSTRUCTOR_PARAMS); c.setAccessible(true); constructors.put(fullName, c); diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java index b0e3d5fe..a18bcf32 100644 --- a/android/support/mediacompat/testlib/IntentConstants.java +++ b/android/support/mediacompat/testlib/IntentConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. diff --git a/android/support/mediacompat/testlib/MediaBrowserConstants.java b/android/support/mediacompat/testlib/MediaBrowserConstants.java index 8ef0a355..86024d90 100644 --- a/android/support/mediacompat/testlib/MediaBrowserConstants.java +++ b/android/support/mediacompat/testlib/MediaBrowserConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. diff --git a/android/support/mediacompat/testlib/MediaControllerConstants.java b/android/support/mediacompat/testlib/MediaControllerConstants.java index 1de00efc..5fa086b3 100644 --- a/android/support/mediacompat/testlib/MediaControllerConstants.java +++ b/android/support/mediacompat/testlib/MediaControllerConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java index 79381e5e..95be1621 100644 --- a/android/support/mediacompat/testlib/MediaSessionConstants.java +++ b/android/support/mediacompat/testlib/MediaSessionConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java index f3773895..ae31c4fb 100644 --- a/android/support/v17/leanback/app/BrowseFragment.java +++ b/android/support/v17/leanback/app/BrowseFragment.java @@ -682,7 +682,6 @@ public class BrowseFragment extends BaseFragment { private ObjectAdapter mAdapter; private PresenterSelector mAdapterPresenter; - private PresenterSelector mWrappingPresenterSelector; private int mHeadersState = HEADERS_ENABLED; private int mBrandColor = Color.TRANSPARENT; @@ -767,7 +766,11 @@ public class BrowseFragment extends BaseFragment { * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow * DividerRow and PageRow. */ - private void createAndSetWrapperPresenter() { + private void updateWrapperPresenter() { + if (mAdapter == null) { + mAdapterPresenter = null; + return; + } final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector(); if (adapterPresenter == null) { throw new IllegalArgumentException("Adapter.getPresenterSelector() is null"); @@ -812,18 +815,16 @@ public class BrowseFragment extends BaseFragment { */ public void setAdapter(ObjectAdapter adapter) { mAdapter = adapter; - createAndSetWrapperPresenter(); + updateWrapperPresenter(); if (getView() == null) { return; } - replaceMainFragment(mSelectedPosition); - if (adapter != null) { - if (mMainFragmentRowsAdapter != null) { - mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter)); - } - mHeadersFragment.setAdapter(adapter); + if (mMainFragmentRowsAdapter != null) { + mMainFragmentRowsAdapter.setAdapter( + adapter == null ? null : new ListRowDataAdapter(adapter)); } + mHeadersFragment.setAdapter(adapter); } public final MainFragmentAdapterRegistry getMainFragmentRegistry() { diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java index 03b3c8a6..4a2502a8 100644 --- a/android/support/v17/leanback/app/BrowseSupportFragment.java +++ b/android/support/v17/leanback/app/BrowseSupportFragment.java @@ -679,7 +679,6 @@ public class BrowseSupportFragment extends BaseSupportFragment { private ObjectAdapter mAdapter; private PresenterSelector mAdapterPresenter; - private PresenterSelector mWrappingPresenterSelector; private int mHeadersState = HEADERS_ENABLED; private int mBrandColor = Color.TRANSPARENT; @@ -764,7 +763,11 @@ public class BrowseSupportFragment extends BaseSupportFragment { * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow * DividerRow and PageRow. */ - private void createAndSetWrapperPresenter() { + private void updateWrapperPresenter() { + if (mAdapter == null) { + mAdapterPresenter = null; + return; + } final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector(); if (adapterPresenter == null) { throw new IllegalArgumentException("Adapter.getPresenterSelector() is null"); @@ -809,18 +812,16 @@ public class BrowseSupportFragment extends BaseSupportFragment { */ public void setAdapter(ObjectAdapter adapter) { mAdapter = adapter; - createAndSetWrapperPresenter(); + updateWrapperPresenter(); if (getView() == null) { return; } - replaceMainFragment(mSelectedPosition); - if (adapter != null) { - if (mMainFragmentRowsAdapter != null) { - mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter)); - } - mHeadersSupportFragment.setAdapter(adapter); + if (mMainFragmentRowsAdapter != null) { + mMainFragmentRowsAdapter.setAdapter( + adapter == null ? null : new ListRowDataAdapter(adapter)); } + mHeadersSupportFragment.setAdapter(adapter); } public final MainFragmentAdapterRegistry getMainFragmentRegistry() { diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java index af37f77a..dded0715 100644 --- a/android/support/v17/leanback/widget/GridLayoutManager.java +++ b/android/support/v17/leanback/widget/GridLayoutManager.java @@ -2693,6 +2693,40 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } } + // Observer is registered on Adapter to invalidate saved instance state + final RecyclerView.AdapterDataObserver mObServer = new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + mChildrenStates.clear(); + } + + @Override + public void onItemRangeChanged(int positionStart, int itemCount) { + if (DEBUG) { + Log.v(getTag(), "onItemRangeChanged positionStart " + + positionStart + " itemCount " + itemCount); + } + for (int i = positionStart, end = positionStart + itemCount; i < end; i++) { + mChildrenStates.remove(i); + } + } + + @Override + public void onItemRangeInserted(int positionStart, int itemCount) { + mChildrenStates.clear(); + } + + @Override + public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { + mChildrenStates.clear(); + } + + @Override + public void onItemRangeRemoved(int positionStart, int itemCount) { + mChildrenStates.clear(); + } + }; + @Override public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart " @@ -2704,14 +2738,12 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPositionOffset += itemCount; } } - mChildrenStates.clear(); } @Override public void onItemsChanged(RecyclerView recyclerView) { if (DEBUG) Log.v(getTag(), "onItemsChanged"); mFocusPositionOffset = 0; - mChildrenStates.clear(); } @Override @@ -2732,7 +2764,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { } } } - mChildrenStates.clear(); } @Override @@ -2753,16 +2784,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPositionOffset += itemCount; } } - mChildrenStates.clear(); - } - - @Override - public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) { - if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart " - + positionStart + " itemCount " + itemCount); - for (int i = positionStart, end = positionStart + itemCount; i < end; i++) { - mChildrenStates.remove(i); - } } @Override @@ -3460,12 +3481,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager { mFocusPosition = NO_POSITION; mFocusPositionOffset = 0; mChildrenStates.clear(); + oldAdapter.unregisterAdapterDataObserver(mObServer); } if (newAdapter instanceof FacetProviderAdapter) { mFacetProviderAdapter = (FacetProviderAdapter) newAdapter; } else { mFacetProviderAdapter = null; } + if (newAdapter != null) { + newAdapter.registerAdapterDataObserver(mObServer); + } super.onAdapterChanged(oldAdapter, newAdapter); } diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java index cb3c59a6..614ff351 100644 --- a/android/support/v4/app/FragmentActivity.java +++ b/android/support/v4/app/FragmentActivity.java @@ -977,7 +977,11 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements continue; } fragment.mLifecycleRegistry.markState(state); - markState(fragment.getChildFragmentManager(), state); + + FragmentManager childFragmentManager = fragment.peekChildFragmentManager(); + if (childFragmentManager != null) { + markState(childFragmentManager, state); + } } } } diff --git a/android/support/v7/widget/TooltipPopup.java b/android/support/v7/widget/TooltipPopup.java index f707c8fc..dc20aa1f 100644 --- a/android/support/v7/widget/TooltipPopup.java +++ b/android/support/v7/widget/TooltipPopup.java @@ -56,7 +56,7 @@ class TooltipPopup { TooltipPopup(Context context) { mContext = context; - mContentView = LayoutInflater.from(mContext).inflate(R.layout.tooltip, null); + mContentView = LayoutInflater.from(mContext).inflate(R.layout.abc_tooltip, null); mMessageView = (TextView) mContentView.findViewById(R.id.message); mLayoutParams.setTitle(getClass().getSimpleName()); diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java index 99f8cfbf..6fc7d23a 100644 --- a/android/telephony/CarrierConfigManager.java +++ b/android/telephony/CarrierConfigManager.java @@ -210,6 +210,12 @@ public class CarrierConfigManager { public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; /** + * Determine whether user can edit voicemail number in Settings. + */ + public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = + "editable_voicemail_number_setting_bool"; + + /** * Since the default voicemail number is empty, if a SIM card does not have a voicemail number * available the user cannot use voicemail. This flag allows the user to edit the voicemail * number in such cases, and is false by default. @@ -1615,6 +1621,13 @@ public class CarrierConfigManager { public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL = "skip_cf_fail_to_disable_dialog_bool"; + /** + * List of the FAC (feature access codes) to dial as a normal call. + * @hide + */ + public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY = + "feature_access_codes_string_array"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1674,6 +1687,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true); sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false); + sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL, true); sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false); sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false); sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false); @@ -1887,6 +1901,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false); + sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null); } /** diff --git a/android/telephony/CellIdentityCdma.java b/android/telephony/CellIdentityCdma.java index b39b4c76..ddc938e6 100644 --- a/android/telephony/CellIdentityCdma.java +++ b/android/telephony/CellIdentityCdma.java @@ -19,6 +19,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import android.text.TextUtils; import java.util.Objects; @@ -50,6 +51,10 @@ public final class CellIdentityCdma implements Parcelable { * to +90 degrees). */ private final int mLatitude; + // long alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaLong; + // short alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaShort; /** * @hide @@ -60,6 +65,8 @@ public final class CellIdentityCdma implements Parcelable { mBasestationId = Integer.MAX_VALUE; mLongitude = Integer.MAX_VALUE; mLatitude = Integer.MAX_VALUE; + mAlphaLong = null; + mAlphaShort = null; } /** @@ -75,19 +82,37 @@ public final class CellIdentityCdma implements Parcelable { * @hide */ public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat) { + this(nid, sid, bid, lon, lat, null, null); + } + + /** + * public constructor + * @param nid Network Id 0..65535 + * @param sid CDMA System Id 0..32767 + * @param bid Base Station Id 0..65535 + * @param lon Longitude is a decimal number ranges from -2592000 + * to 2592000 + * @param lat Latitude is a decimal number ranges from -1296000 + * to 1296000 + * @param alphal long alpha Operator Name String or Enhanced Operator Name String + * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * + * @hide + */ + public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat, String alphal, + String alphas) { mNetworkId = nid; mSystemId = sid; mBasestationId = bid; mLongitude = lon; mLatitude = lat; + mAlphaLong = alphal; + mAlphaShort = alphas; } private CellIdentityCdma(CellIdentityCdma cid) { - mNetworkId = cid.mNetworkId; - mSystemId = cid.mSystemId; - mBasestationId = cid.mBasestationId; - mLongitude = cid.mLongitude; - mLatitude = cid.mLatitude; + this(cid.mNetworkId, cid.mSystemId, cid.mBasestationId, cid.mLongitude, cid.mLatitude, + cid.mAlphaLong, cid.mAlphaShort); } CellIdentityCdma copy() { @@ -137,9 +162,26 @@ public final class CellIdentityCdma implements Parcelable { return mLatitude; } + /** + * @return The long alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaLong() { + return mAlphaLong; + } + + /** + * @return The short alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaShort() { + return mAlphaShort; + } + @Override public int hashCode() { - return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude); + return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude, + mAlphaLong, mAlphaShort); } @Override @@ -153,11 +195,14 @@ public final class CellIdentityCdma implements Parcelable { } CellIdentityCdma o = (CellIdentityCdma) other; + return mNetworkId == o.mNetworkId && mSystemId == o.mSystemId && mBasestationId == o.mBasestationId && mLatitude == o.mLatitude && - mLongitude == o.mLongitude; + mLongitude == o.mLongitude && + TextUtils.equals(mAlphaLong, o.mAlphaLong) && + TextUtils.equals(mAlphaShort, o.mAlphaShort); } @Override @@ -168,6 +213,8 @@ public final class CellIdentityCdma implements Parcelable { sb.append(" mBasestationId="); sb.append(mBasestationId); sb.append(" mLongitude="); sb.append(mLongitude); sb.append(" mLatitude="); sb.append(mLatitude); + sb.append(" mAlphaLong="); sb.append(mAlphaLong); + sb.append(" mAlphaShort="); sb.append(mAlphaShort); sb.append("}"); return sb.toString(); @@ -188,15 +235,15 @@ public final class CellIdentityCdma implements Parcelable { dest.writeInt(mBasestationId); dest.writeInt(mLongitude); dest.writeInt(mLatitude); + dest.writeString(mAlphaLong); + dest.writeString(mAlphaShort); } /** Construct from Parcel, type has already been processed */ private CellIdentityCdma(Parcel in) { - mNetworkId = in.readInt(); - mSystemId = in.readInt(); - mBasestationId = in.readInt(); - mLongitude = in.readInt(); - mLatitude = in.readInt(); + this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readInt(), + in.readString(), in.readString()); + if (DBG) log("CellIdentityCdma(Parcel): " + toString()); } diff --git a/android/telephony/CellIdentityGsm.java b/android/telephony/CellIdentityGsm.java index ec008e28..6276626a 100644 --- a/android/telephony/CellIdentityGsm.java +++ b/android/telephony/CellIdentityGsm.java @@ -19,6 +19,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import android.text.TextUtils; import java.util.Objects; @@ -30,10 +31,6 @@ public final class CellIdentityGsm implements Parcelable { private static final String LOG_TAG = "CellIdentityGsm"; private static final boolean DBG = false; - // 3-digit Mobile Country Code, 0..999 - private final int mMcc; - // 2 or 3-digit Mobile Network Code, 0..999 - private final int mMnc; // 16-bit Location Area Code, 0..65535 private final int mLac; // 16-bit GSM Cell Identity described in TS 27.007, 0..65535 @@ -42,17 +39,27 @@ public final class CellIdentityGsm implements Parcelable { private final int mArfcn; // 6-bit Base Station Identity Code private final int mBsic; + // 3-digit Mobile Country Code in string format + private final String mMccStr; + // 2 or 3-digit Mobile Network Code in string format + private final String mMncStr; + // long alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaLong; + // short alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaShort; /** * @hide */ public CellIdentityGsm() { - mMcc = Integer.MAX_VALUE; - mMnc = Integer.MAX_VALUE; mLac = Integer.MAX_VALUE; mCid = Integer.MAX_VALUE; mArfcn = Integer.MAX_VALUE; mBsic = Integer.MAX_VALUE; + mMccStr = null; + mMncStr = null; + mAlphaLong = null; + mAlphaShort = null; } /** * public constructor @@ -64,7 +71,8 @@ public final class CellIdentityGsm implements Parcelable { * @hide */ public CellIdentityGsm (int mcc, int mnc, int lac, int cid) { - this(mcc, mnc, lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE); + this(lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE, + String.valueOf(mcc), String.valueOf(mnc), null, null); } /** @@ -79,39 +87,81 @@ public final class CellIdentityGsm implements Parcelable { * @hide */ public CellIdentityGsm (int mcc, int mnc, int lac, int cid, int arfcn, int bsic) { - mMcc = mcc; - mMnc = mnc; + this(lac, cid, arfcn, bsic, String.valueOf(mcc), String.valueOf(mnc), null, null); + } + + /** + * public constructor + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity + * @param arfcn 16-bit GSM Absolute RF Channel Number + * @param bsic 6-bit Base Station Identity Code + * @param mccStr 3-digit Mobile Country Code in string format + * @param mncStr 2 or 3-digit Mobile Network Code in string format + * @param alphal long alpha Operator Name String or Enhanced Operator Name String + * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * + * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is + * not a 2 or 3-digit code. + * + * @hide + */ + public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr, + String mncStr, String alphal, String alphas) { mLac = lac; mCid = cid; mArfcn = arfcn; - mBsic = bsic; + // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator + // for inbound parcels + mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic; + + if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { + mMccStr = mccStr; + } else if (mccStr.isEmpty()) { + // If the mccStr parsed from Parcel is empty, set it as null. + mMccStr = null; + } else { + throw new IllegalArgumentException("invalid MCC format"); + } + + if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { + mMncStr = mncStr; + } else if (mncStr.isEmpty()) { + // If the mncStr parsed from Parcel is empty, set it as null. + mMncStr = null; + } else { + throw new IllegalArgumentException("invalid MNC format"); + } + + mAlphaLong = alphal; + mAlphaShort = alphas; } private CellIdentityGsm(CellIdentityGsm cid) { - mMcc = cid.mMcc; - mMnc = cid.mMnc; - mLac = cid.mLac; - mCid = cid.mCid; - mArfcn = cid.mArfcn; - mBsic = cid.mBsic; + this(cid.mLac, cid.mCid, cid.mArfcn, cid.mBsic, cid.mMccStr, + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); } CellIdentityGsm copy() { - return new CellIdentityGsm(this); + return new CellIdentityGsm(this); } /** * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMccStr} instead. */ + @Deprecated public int getMcc() { - return mMcc; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; } /** * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMncStr} instead. */ + @Deprecated public int getMnc() { - return mMnc; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; } /** @@ -144,6 +194,43 @@ public final class CellIdentityGsm implements Parcelable { return mBsic; } + /** + * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown + */ + public String getMobileNetworkOperator() { + return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr; + } + + /** + * @return Mobile Country Code in string format, null if unknown + */ + public String getMccStr() { + return mMccStr; + } + + /** + * @return Mobile Network Code in string format, null if unknown + */ + public String getMncStr() { + return mMncStr; + } + + /** + * @return The long alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaLong() { + return mAlphaLong; + } + + /** + * @return The short alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaShort() { + return mAlphaShort; + } + /** * @return Integer.MAX_VALUE, undefined for GSM @@ -155,7 +242,7 @@ public final class CellIdentityGsm implements Parcelable { @Override public int hashCode() { - return Objects.hash(mMcc, mMnc, mLac, mCid); + return Objects.hash(mMccStr, mMncStr, mLac, mCid, mAlphaLong, mAlphaShort); } @Override @@ -169,23 +256,27 @@ public final class CellIdentityGsm implements Parcelable { } CellIdentityGsm o = (CellIdentityGsm) other; - return mMcc == o.mMcc && - mMnc == o.mMnc && - mLac == o.mLac && + return mLac == o.mLac && mCid == o.mCid && mArfcn == o.mArfcn && - mBsic == o.mBsic; + mBsic == o.mBsic && + TextUtils.equals(mMccStr, o.mMccStr) && + TextUtils.equals(mMncStr, o.mMncStr) && + TextUtils.equals(mAlphaLong, o.mAlphaLong) && + TextUtils.equals(mAlphaShort, o.mAlphaShort); } @Override public String toString() { StringBuilder sb = new StringBuilder("CellIdentityGsm:{"); - sb.append(" mMcc=").append(mMcc); - sb.append(" mMnc=").append(mMnc); sb.append(" mLac=").append(mLac); sb.append(" mCid=").append(mCid); sb.append(" mArfcn=").append(mArfcn); sb.append(" mBsic=").append("0x").append(Integer.toHexString(mBsic)); + sb.append(" mMcc=").append(mMccStr); + sb.append(" mMnc=").append(mMncStr); + sb.append(" mAlphaLong=").append(mAlphaLong); + sb.append(" mAlphaShort=").append(mAlphaShort); sb.append("}"); return sb.toString(); @@ -201,26 +292,20 @@ public final class CellIdentityGsm implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { if (DBG) log("writeToParcel(Parcel, int): " + toString()); - dest.writeInt(mMcc); - dest.writeInt(mMnc); dest.writeInt(mLac); dest.writeInt(mCid); dest.writeInt(mArfcn); dest.writeInt(mBsic); + dest.writeString(mMccStr); + dest.writeString(mMncStr); + dest.writeString(mAlphaLong); + dest.writeString(mAlphaShort); } /** Construct from Parcel, type has already been processed */ private CellIdentityGsm(Parcel in) { - mMcc = in.readInt(); - mMnc = in.readInt(); - mLac = in.readInt(); - mCid = in.readInt(); - mArfcn = in.readInt(); - int bsic = in.readInt(); - // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator - // for inbound parcels - if (bsic == 0xFF) bsic = Integer.MAX_VALUE; - mBsic = bsic; + this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(), + in.readString(), in.readString(), in.readString()); if (DBG) log("CellIdentityGsm(Parcel): " + toString()); } @@ -229,16 +314,16 @@ public final class CellIdentityGsm implements Parcelable { @SuppressWarnings("hiding") public static final Creator<CellIdentityGsm> CREATOR = new Creator<CellIdentityGsm>() { - @Override - public CellIdentityGsm createFromParcel(Parcel in) { - return new CellIdentityGsm(in); - } + @Override + public CellIdentityGsm createFromParcel(Parcel in) { + return new CellIdentityGsm(in); + } - @Override - public CellIdentityGsm[] newArray(int size) { - return new CellIdentityGsm[size]; - } - }; + @Override + public CellIdentityGsm[] newArray(int size) { + return new CellIdentityGsm[size]; + } + }; /** * log @@ -246,4 +331,4 @@ public final class CellIdentityGsm implements Parcelable { private static void log(String s) { Rlog.w(LOG_TAG, s); } -} +}
\ No newline at end of file diff --git a/android/telephony/CellIdentityLte.java b/android/telephony/CellIdentityLte.java index ce743835..74d2966b 100644 --- a/android/telephony/CellIdentityLte.java +++ b/android/telephony/CellIdentityLte.java @@ -19,6 +19,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import android.text.TextUtils; import java.util.Objects; @@ -30,10 +31,6 @@ public final class CellIdentityLte implements Parcelable { private static final String LOG_TAG = "CellIdentityLte"; private static final boolean DBG = false; - // 3-digit Mobile Country Code, 0..999 - private final int mMcc; - // 2 or 3-digit Mobile Network Code, 0..999 - private final int mMnc; // 28-bit cell identity private final int mCi; // physical cell id 0..503 @@ -42,17 +39,27 @@ public final class CellIdentityLte implements Parcelable { private final int mTac; // 18-bit Absolute RF Channel Number private final int mEarfcn; + // 3-digit Mobile Country Code in string format + private final String mMccStr; + // 2 or 3-digit Mobile Network Code in string format + private final String mMncStr; + // long alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaLong; + // short alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaShort; /** * @hide */ public CellIdentityLte() { - mMcc = Integer.MAX_VALUE; - mMnc = Integer.MAX_VALUE; mCi = Integer.MAX_VALUE; mPci = Integer.MAX_VALUE; mTac = Integer.MAX_VALUE; mEarfcn = Integer.MAX_VALUE; + mMccStr = null; + mMncStr = null; + mAlphaLong = null; + mAlphaShort = null; } /** @@ -66,7 +73,7 @@ public final class CellIdentityLte implements Parcelable { * @hide */ public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac) { - this(mcc, mnc, ci, pci, tac, Integer.MAX_VALUE); + this(ci, pci, tac, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), null, null); } /** @@ -81,21 +88,57 @@ public final class CellIdentityLte implements Parcelable { * @hide */ public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac, int earfcn) { - mMcc = mcc; - mMnc = mnc; + this(ci, pci, tac, earfcn, String.valueOf(mcc), String.valueOf(mnc), null, null); + } + + /** + * + * @param ci 28-bit Cell Identity + * @param pci Physical Cell Id 0..503 + * @param tac 16-bit Tracking Area Code + * @param earfcn 18-bit LTE Absolute RF Channel Number + * @param mccStr 3-digit Mobile Country Code in string format + * @param mncStr 2 or 3-digit Mobile Network Code in string format + * @param alphal long alpha Operator Name String or Enhanced Operator Name String + * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * + * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is + * not a 2 or 3-digit code. + * + * @hide + */ + public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr, + String mncStr, String alphal, String alphas) { mCi = ci; mPci = pci; mTac = tac; mEarfcn = earfcn; + + if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { + mMccStr = mccStr; + } else if (mccStr.isEmpty()) { + // If the mccStr parsed from Parcel is empty, set it as null. + mMccStr = null; + } else { + throw new IllegalArgumentException("invalid MCC format"); + } + + if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { + mMncStr = mncStr; + } else if (mncStr.isEmpty()) { + // If the mncStr parsed from Parcel is empty, set it as null. + mMncStr = null; + } else { + throw new IllegalArgumentException("invalid MNC format"); + } + + mAlphaLong = alphal; + mAlphaShort = alphas; } private CellIdentityLte(CellIdentityLte cid) { - mMcc = cid.mMcc; - mMnc = cid.mMnc; - mCi = cid.mCi; - mPci = cid.mPci; - mTac = cid.mTac; - mEarfcn = cid.mEarfcn; + this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mMccStr, + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); } CellIdentityLte copy() { @@ -104,16 +147,20 @@ public final class CellIdentityLte implements Parcelable { /** * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMccStr} instead. */ + @Deprecated public int getMcc() { - return mMcc; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; } /** * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMncStr} instead. */ + @Deprecated public int getMnc() { - return mMnc; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; } /** @@ -144,9 +191,46 @@ public final class CellIdentityLte implements Parcelable { return mEarfcn; } + /** + * @return Mobile Country Code in string format, null if unknown + */ + public String getMccStr() { + return mMccStr; + } + + /** + * @return Mobile Network Code in string format, null if unknown + */ + public String getMncStr() { + return mMncStr; + } + + /** + * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown + */ + public String getMobileNetworkOperator() { + return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr; + } + + /** + * @return The long alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaLong() { + return mAlphaLong; + } + + /** + * @return The short alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaShort() { + return mAlphaShort; + } + @Override public int hashCode() { - return Objects.hash(mMcc, mMnc, mCi, mPci, mTac); + return Objects.hash(mMccStr, mMncStr, mCi, mPci, mTac, mAlphaLong, mAlphaShort); } @Override @@ -160,23 +244,27 @@ public final class CellIdentityLte implements Parcelable { } CellIdentityLte o = (CellIdentityLte) other; - return mMcc == o.mMcc && - mMnc == o.mMnc && - mCi == o.mCi && + return mCi == o.mCi && mPci == o.mPci && mTac == o.mTac && - mEarfcn == o.mEarfcn; + mEarfcn == o.mEarfcn && + TextUtils.equals(mMccStr, o.mMccStr) && + TextUtils.equals(mMncStr, o.mMncStr) && + TextUtils.equals(mAlphaLong, o.mAlphaLong) && + TextUtils.equals(mAlphaShort, o.mAlphaShort); } @Override public String toString() { StringBuilder sb = new StringBuilder("CellIdentityLte:{"); - sb.append(" mMcc="); sb.append(mMcc); - sb.append(" mMnc="); sb.append(mMnc); sb.append(" mCi="); sb.append(mCi); sb.append(" mPci="); sb.append(mPci); sb.append(" mTac="); sb.append(mTac); sb.append(" mEarfcn="); sb.append(mEarfcn); + sb.append(" mMcc="); sb.append(mMccStr); + sb.append(" mMnc="); sb.append(mMncStr); + sb.append(" mAlphaLong="); sb.append(mAlphaLong); + sb.append(" mAlphaShort="); sb.append(mAlphaShort); sb.append("}"); return sb.toString(); @@ -192,22 +280,21 @@ public final class CellIdentityLte implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { if (DBG) log("writeToParcel(Parcel, int): " + toString()); - dest.writeInt(mMcc); - dest.writeInt(mMnc); dest.writeInt(mCi); dest.writeInt(mPci); dest.writeInt(mTac); dest.writeInt(mEarfcn); + dest.writeString(mMccStr); + dest.writeString(mMncStr); + dest.writeString(mAlphaLong); + dest.writeString(mAlphaShort); } /** Construct from Parcel, type has already been processed */ private CellIdentityLte(Parcel in) { - mMcc = in.readInt(); - mMnc = in.readInt(); - mCi = in.readInt(); - mPci = in.readInt(); - mTac = in.readInt(); - mEarfcn = in.readInt(); + this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(), + in.readString(), in.readString(), in.readString()); + if (DBG) log("CellIdentityLte(Parcel): " + toString()); } @@ -215,16 +302,16 @@ public final class CellIdentityLte implements Parcelable { @SuppressWarnings("hiding") public static final Creator<CellIdentityLte> CREATOR = new Creator<CellIdentityLte>() { - @Override - public CellIdentityLte createFromParcel(Parcel in) { - return new CellIdentityLte(in); - } + @Override + public CellIdentityLte createFromParcel(Parcel in) { + return new CellIdentityLte(in); + } - @Override - public CellIdentityLte[] newArray(int size) { - return new CellIdentityLte[size]; - } - }; + @Override + public CellIdentityLte[] newArray(int size) { + return new CellIdentityLte[size]; + } + }; /** * log @@ -232,4 +319,4 @@ public final class CellIdentityLte implements Parcelable { private static void log(String s) { Rlog.w(LOG_TAG, s); } -} +}
\ No newline at end of file diff --git a/android/telephony/CellIdentityWcdma.java b/android/telephony/CellIdentityWcdma.java index 0d13efd2..51b11aa8 100644 --- a/android/telephony/CellIdentityWcdma.java +++ b/android/telephony/CellIdentityWcdma.java @@ -19,6 +19,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import android.text.TextUtils; import java.util.Objects; @@ -30,10 +31,6 @@ public final class CellIdentityWcdma implements Parcelable { private static final String LOG_TAG = "CellIdentityWcdma"; private static final boolean DBG = false; - // 3-digit Mobile Country Code, 0..999 - private final int mMcc; - // 2 or 3-digit Mobile Network Code, 0..999 - private final int mMnc; // 16-bit Location Area Code, 0..65535 private final int mLac; // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455 @@ -42,17 +39,27 @@ public final class CellIdentityWcdma implements Parcelable { private final int mPsc; // 16-bit UMTS Absolute RF Channel Number private final int mUarfcn; + // 3-digit Mobile Country Code in string format + private final String mMccStr; + // 2 or 3-digit Mobile Network Code in string format + private final String mMncStr; + // long alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaLong; + // short alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaShort; /** * @hide */ public CellIdentityWcdma() { - mMcc = Integer.MAX_VALUE; - mMnc = Integer.MAX_VALUE; mLac = Integer.MAX_VALUE; mCid = Integer.MAX_VALUE; mPsc = Integer.MAX_VALUE; mUarfcn = Integer.MAX_VALUE; + mMccStr = null; + mMncStr = null; + mAlphaLong = null; + mAlphaShort = null; } /** * public constructor @@ -65,7 +72,8 @@ public final class CellIdentityWcdma implements Parcelable { * @hide */ public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) { - this(mcc, mnc, lac, cid, psc, Integer.MAX_VALUE); + this(lac, cid, psc, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), + null, null); } /** @@ -80,39 +88,79 @@ public final class CellIdentityWcdma implements Parcelable { * @hide */ public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc, int uarfcn) { - mMcc = mcc; - mMnc = mnc; + this(lac, cid, psc, uarfcn, String.valueOf(mcc), String.valueOf(mnc), null, null); + } + + /** + * public constructor + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 28-bit UMTS Cell Identity + * @param psc 9-bit UMTS Primary Scrambling Code + * @param uarfcn 16-bit UMTS Absolute RF Channel Number + * @param mccStr 3-digit Mobile Country Code in string format + * @param mncStr 2 or 3-digit Mobile Network Code in string format + * @param alphal long alpha Operator Name String or Enhanced Operator Name String + * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * + * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is + * not a 2 or 3-digit code. + * + * @hide + */ + public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn, + String mccStr, String mncStr, String alphal, String alphas) { mLac = lac; mCid = cid; mPsc = psc; mUarfcn = uarfcn; + + if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { + mMccStr = mccStr; + } else if (mccStr.isEmpty()) { + // If the mccStr parsed from Parcel is empty, set it as null. + mMccStr = null; + } else { + throw new IllegalArgumentException("invalid MCC format"); + } + + if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { + mMncStr = mncStr; + } else if (mncStr.isEmpty()) { + // If the mncStr parsed from Parcel is empty, set it as null. + mMncStr = null; + } else { + throw new IllegalArgumentException("invalid MNC format"); + } + + mAlphaLong = alphal; + mAlphaShort = alphas; } private CellIdentityWcdma(CellIdentityWcdma cid) { - mMcc = cid.mMcc; - mMnc = cid.mMnc; - mLac = cid.mLac; - mCid = cid.mCid; - mPsc = cid.mPsc; - mUarfcn = cid.mUarfcn; + this(cid.mLac, cid.mCid, cid.mPsc, cid.mUarfcn, cid.mMccStr, + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); } CellIdentityWcdma copy() { - return new CellIdentityWcdma(this); + return new CellIdentityWcdma(this); } /** * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMccStr} instead. */ + @Deprecated public int getMcc() { - return mMcc; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; } /** * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMncStr} instead. */ + @Deprecated public int getMnc() { - return mMnc; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; } /** @@ -138,9 +186,46 @@ public final class CellIdentityWcdma implements Parcelable { return mPsc; } + /** + * @return Mobile Country Code in string version, null if unknown + */ + public String getMccStr() { + return mMccStr; + } + + /** + * @return Mobile Network Code in string version, null if unknown + */ + public String getMncStr() { + return mMncStr; + } + + /** + * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown + */ + public String getMobileNetworkOperator() { + return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr; + } + + /** + * @return The long alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaLong() { + return mAlphaLong; + } + + /** + * @return The short alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaShort() { + return mAlphaShort; + } + @Override public int hashCode() { - return Objects.hash(mMcc, mMnc, mLac, mCid, mPsc); + return Objects.hash(mMccStr, mMncStr, mLac, mCid, mPsc, mAlphaLong, mAlphaShort); } /** @@ -161,23 +246,27 @@ public final class CellIdentityWcdma implements Parcelable { } CellIdentityWcdma o = (CellIdentityWcdma) other; - return mMcc == o.mMcc && - mMnc == o.mMnc && - mLac == o.mLac && + return mLac == o.mLac && mCid == o.mCid && mPsc == o.mPsc && - mUarfcn == o.mUarfcn; + mUarfcn == o.mUarfcn && + TextUtils.equals(mMccStr, o.mMccStr) && + TextUtils.equals(mMncStr, o.mMncStr) && + TextUtils.equals(mAlphaLong, o.mAlphaLong) && + TextUtils.equals(mAlphaShort, o.mAlphaShort); } @Override public String toString() { StringBuilder sb = new StringBuilder("CellIdentityWcdma:{"); - sb.append(" mMcc=").append(mMcc); - sb.append(" mMnc=").append(mMnc); sb.append(" mLac=").append(mLac); sb.append(" mCid=").append(mCid); sb.append(" mPsc=").append(mPsc); sb.append(" mUarfcn=").append(mUarfcn); + sb.append(" mMcc=").append(mMccStr); + sb.append(" mMnc=").append(mMncStr); + sb.append(" mAlphaLong=").append(mAlphaLong); + sb.append(" mAlphaShort=").append(mAlphaShort); sb.append("}"); return sb.toString(); @@ -193,22 +282,21 @@ public final class CellIdentityWcdma implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { if (DBG) log("writeToParcel(Parcel, int): " + toString()); - dest.writeInt(mMcc); - dest.writeInt(mMnc); dest.writeInt(mLac); dest.writeInt(mCid); dest.writeInt(mPsc); dest.writeInt(mUarfcn); + dest.writeString(mMccStr); + dest.writeString(mMncStr); + dest.writeString(mAlphaLong); + dest.writeString(mAlphaShort); } /** Construct from Parcel, type has already been processed */ private CellIdentityWcdma(Parcel in) { - mMcc = in.readInt(); - mMnc = in.readInt(); - mLac = in.readInt(); - mCid = in.readInt(); - mPsc = in.readInt(); - mUarfcn = in.readInt(); + this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(), + in.readString(), in.readString(), in.readString()); + if (DBG) log("CellIdentityWcdma(Parcel): " + toString()); } @@ -216,16 +304,16 @@ public final class CellIdentityWcdma implements Parcelable { @SuppressWarnings("hiding") public static final Creator<CellIdentityWcdma> CREATOR = new Creator<CellIdentityWcdma>() { - @Override - public CellIdentityWcdma createFromParcel(Parcel in) { - return new CellIdentityWcdma(in); - } + @Override + public CellIdentityWcdma createFromParcel(Parcel in) { + return new CellIdentityWcdma(in); + } - @Override - public CellIdentityWcdma[] newArray(int size) { - return new CellIdentityWcdma[size]; - } - }; + @Override + public CellIdentityWcdma[] newArray(int size) { + return new CellIdentityWcdma[size]; + } + }; /** * log @@ -233,4 +321,4 @@ public final class CellIdentityWcdma implements Parcelable { private static void log(String s) { Rlog.w(LOG_TAG, s); } -} +}
\ No newline at end of file diff --git a/android/telephony/DisconnectCause.java b/android/telephony/DisconnectCause.java index 98fb6534..c3a2ceb1 100644 --- a/android/telephony/DisconnectCause.java +++ b/android/telephony/DisconnectCause.java @@ -273,6 +273,13 @@ public class DisconnectCause { * {@hide} */ public static final int EMERGENCY_PERM_FAILURE = 64; + + /** + * This cause is used to report a normal event only when no other cause in the normal class + * applies. + * {@hide} + */ + public static final int NORMAL_UNSPECIFIED = 65; //********************************************************************************************* // When adding a disconnect type: // 1) Update toString() with the newly added disconnect type. @@ -413,6 +420,8 @@ public class DisconnectCause { return "EMERGENCY_TEMP_FAILURE"; case EMERGENCY_PERM_FAILURE: return "EMERGENCY_PERM_FAILURE"; + case NORMAL_UNSPECIFIED: + return "NORMAL_UNSPECIFIED"; default: return "INVALID: " + cause; } diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java index 9a9877a8..f392570e 100644 --- a/android/telephony/MbmsDownloadSession.java +++ b/android/telephony/MbmsDownloadSession.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -74,6 +75,14 @@ public class MbmsDownloadSession implements AutoCloseable { "android.telephony.action.EmbmsDownload"; /** + * Metadata key that specifies the component name of the service to bind to for file-download. + * @hide + */ + @TestApi + public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = + "mbms-download-service-override"; + + /** * Integer extra that Android will attach to the intent supplied via * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)} * Indicates the result code of the download. One of diff --git a/android/telephony/MbmsStreamingSession.java b/android/telephony/MbmsStreamingSession.java index a8c46079..fb2ff7b1 100644 --- a/android/telephony/MbmsStreamingSession.java +++ b/android/telephony/MbmsStreamingSession.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; @@ -62,6 +63,14 @@ public class MbmsStreamingSession implements AutoCloseable { public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming"; + /** + * Metadata key that specifies the component name of the service to bind to for file-download. + * @hide + */ + @TestApi + public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = + "mbms-streaming-service-override"; + private static AtomicBoolean sIsInitialized = new AtomicBoolean(false); private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null); diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java index 6029995f..98195ada 100644 --- a/android/telephony/SmsManager.java +++ b/android/telephony/SmsManager.java @@ -390,20 +390,23 @@ public final class SmsManager { * Inject an SMS PDU into the android application framework. * * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier - * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges + * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}. * * @param pdu is the byte array of pdu to be injected into android application framework - * @param format is the format of SMS pdu (3gpp or 3gpp2) + * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or + * {@link SmsMessage#FORMAT_3GPP2}) * @param receivedIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is successfully received by the * android application framework, or failed. This intent is broadcasted at * the same time an SMS received from radio is acknowledged back. - * The result code will be <code>RESULT_SMS_HANDLED</code> for success, or - * <code>RESULT_SMS_GENERIC_ERROR</code> for error. + * The result code will be {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_HANDLED} + * for success, or {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_GENERIC_ERROR} for + * error. * - * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2. + * @throws IllegalArgumentException if the format is invalid. */ - public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) { + public void injectSmsPdu( + byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) { if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) { // Format must be either 3gpp or 3gpp2. throw new IllegalArgumentException( diff --git a/android/telephony/SmsMessage.java b/android/telephony/SmsMessage.java index dcdda868..df412335 100644 --- a/android/telephony/SmsMessage.java +++ b/android/telephony/SmsMessage.java @@ -16,24 +16,25 @@ package android.telephony; -import android.os.Binder; -import android.os.Parcel; +import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; + +import android.annotation.StringDef; import android.content.res.Resources; +import android.os.Binder; import android.text.TextUtils; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; +import com.android.internal.telephony.Sms7BitEncodingTranslator; import com.android.internal.telephony.SmsConstants; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; -import com.android.internal.telephony.Sms7BitEncodingTranslator; -import java.lang.Math; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; - /** * A Short Message Service message. @@ -81,15 +82,18 @@ public class SmsMessage { */ public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153; + /** @hide */ + @StringDef({FORMAT_3GPP, FORMAT_3GPP2}) + @Retention(RetentionPolicy.SOURCE) + public @interface Format {} + /** * Indicates a 3GPP format SMS message. - * @hide pending API council approval */ public static final String FORMAT_3GPP = "3gpp"; /** * Indicates a 3GPP2 format SMS message. - * @hide pending API council approval */ public static final String FORMAT_3GPP2 = "3gpp2"; diff --git a/android/telephony/mbms/MbmsUtils.java b/android/telephony/mbms/MbmsUtils.java index d38d8a71..b4ad1d77 100644 --- a/android/telephony/mbms/MbmsUtils.java +++ b/android/telephony/mbms/MbmsUtils.java @@ -22,6 +22,8 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.*; import android.content.pm.ServiceInfo; +import android.telephony.MbmsDownloadSession; +import android.telephony.MbmsStreamingSession; import android.util.Log; import java.io.File; @@ -48,24 +50,64 @@ public class MbmsUtils { return new ComponentName(ci.packageName, ci.name); } + private static ComponentName getOverrideServiceName(Context context, String serviceAction) { + String metaDataKey = null; + switch (serviceAction) { + case MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION: + metaDataKey = MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA; + break; + case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION: + metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA; + break; + } + if (metaDataKey == null) { + return null; + } + + ApplicationInfo appInfo; + try { + appInfo = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + if (appInfo.metaData == null) { + return null; + } + String serviceComponent = appInfo.metaData.getString(metaDataKey); + if (serviceComponent == null) { + return null; + } + return ComponentName.unflattenFromString(serviceComponent); + } + public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) { // Query for the proper service PackageManager packageManager = context.getPackageManager(); Intent queryIntent = new Intent(); queryIntent.setAction(serviceAction); - List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent, - PackageManager.MATCH_SYSTEM_ONLY); - if (downloadServices == null || downloadServices.size() == 0) { - Log.w(LOG_TAG, "No download services found, cannot get service info"); + ComponentName overrideService = getOverrideServiceName(context, serviceAction); + List<ResolveInfo> services; + if (overrideService == null) { + services = packageManager.queryIntentServices(queryIntent, + PackageManager.MATCH_SYSTEM_ONLY); + } else { + queryIntent.setComponent(overrideService); + services = packageManager.queryIntentServices(queryIntent, + PackageManager.MATCH_ALL); + } + + if (services == null || services.size() == 0) { + Log.w(LOG_TAG, "No MBMS services found, cannot get service info"); return null; } - if (downloadServices.size() > 1) { - Log.w(LOG_TAG, "More than one download service found, cannot get unique service"); + if (services.size() > 1) { + Log.w(LOG_TAG, "More than one MBMS service found, cannot get unique service"); return null; } - return downloadServices.get(0).serviceInfo; + return services.get(0).serviceInfo; } public static int startBinding(Context context, String serviceAction, diff --git a/android/telephony/mbms/StreamingServiceInfo.java b/android/telephony/mbms/StreamingServiceInfo.java index c704f346..ef2a14aa 100644 --- a/android/telephony/mbms/StreamingServiceInfo.java +++ b/android/telephony/mbms/StreamingServiceInfo.java @@ -17,6 +17,7 @@ package android.telephony.mbms; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -41,6 +42,7 @@ public final class StreamingServiceInfo extends ServiceInfo implements Parcelabl * @hide */ @SystemApi + @TestApi public StreamingServiceInfo(Map<Locale, String> names, String className, List<Locale> locales, String serviceId, Date start, Date end) { super(names, className, locales, serviceId, start, end); diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java index a2381536..db177c0c 100644 --- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java +++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java @@ -18,6 +18,7 @@ package android.telephony.mbms.vendor; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.Intent; import android.net.Uri; import android.os.Binder; @@ -38,6 +39,7 @@ import java.util.List; * @hide */ @SystemApi +@TestApi public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { /** * Initialize streaming service for this app and subId, registering the listener. diff --git a/android/text/Layout.java b/android/text/Layout.java index ac5c2e92..4d2a9629 100644 --- a/android/text/Layout.java +++ b/android/text/Layout.java @@ -1910,7 +1910,7 @@ public abstract class Layout { MeasuredText mt = MeasuredText.obtain(); TextLine tl = TextLine.obtain(); try { - mt.setPara(text, start, end, textDir, null); + mt.setPara(text, start, end, textDir); Directions directions; int dir; if (mt.mEasy) { diff --git a/android/text/MeasuredText.java b/android/text/MeasuredText.java index ffc44a72..3d9fba71 100644 --- a/android/text/MeasuredText.java +++ b/android/text/MeasuredText.java @@ -39,7 +39,6 @@ class MeasuredText { private int mPos; private TextPaint mWorkPaint; - private StaticLayout.Builder mBuilder; private MeasuredText() { mWorkPaint = new TextPaint(); @@ -82,7 +81,6 @@ class MeasuredText { void finish() { mText = null; - mBuilder = null; if (mLen > 1000) { mWidths = null; mChars = null; @@ -93,9 +91,7 @@ class MeasuredText { /** * Analyzes text for bidirectional runs. Allocates working buffers. */ - void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir, - StaticLayout.Builder builder) { - mBuilder = builder; + void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) { mText = text; mTextStart = start; @@ -159,12 +155,12 @@ class MeasuredText { /** * Apply the style. * - * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled - * text width. - * If StaticLayout.Builder is provided in setPara() method, this method just passes the style - * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0. + * If nativeStaticLayoutPtr is 0, this method measures the styled text width. + * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native + * code by calling StaticLayout.addstyleRun() and returns 0. */ - float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) { + float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm, + long nativeStaticLayoutPtr) { if (fm != null) { paint.getFontMetricsInt(fm); } @@ -174,10 +170,10 @@ class MeasuredText { if (mEasy) { final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT; - if (mBuilder == null) { + if (nativeStaticLayoutPtr == 0) { return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p); } else { - mBuilder.addStyleRun(paint, p, p + len, isRtl); + StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl); return 0.0f; // Builder.addStyleRun doesn't return the width. } } @@ -187,12 +183,12 @@ class MeasuredText { for (int q = p, i = p + 1, e = p + len;; ++i) { if (i == e || mLevels[i] != level) { final boolean isRtl = (level & 0x1) != 0; - if (mBuilder == null) { + if (nativeStaticLayoutPtr == 0) { totalAdvance += paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q); } else { // Builder.addStyleRun doesn't return the width. - mBuilder.addStyleRun(paint, q, i, isRtl); + StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl); } if (i == e) { break; @@ -201,11 +197,15 @@ class MeasuredText { level = mLevels[i]; } } - return totalAdvance; // If mBuilder is null, the result is zero. + return totalAdvance; // If nativeStaticLayoutPtr is 0, the result is zero. + } + + float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) { + return addStyleRun(paint, len, fm, 0 /* native ptr */); } float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, - Paint.FontMetricsInt fm) { + Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) { TextPaint workPaint = mWorkPaint; workPaint.set(paint); @@ -224,18 +224,18 @@ class MeasuredText { float wid; if (replacement == null) { - wid = addStyleRun(workPaint, len, fm); + wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr); } else { // Use original text. Shouldn't matter. wid = replacement.getSize(workPaint, mText, mTextStart + mPos, mTextStart + mPos + len, fm); - if (mBuilder == null) { + if (nativeStaticLayoutPtr == 0) { float[] w = mWidths; w[mPos] = wid; for (int i = mPos + 1, e = mPos + len; i < e; i++) w[i] = 0; } else { - mBuilder.addReplacementRun(paint, mPos, mPos + len, wid); + StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid); } mPos += len; } @@ -253,6 +253,11 @@ class MeasuredText { return wid; } + float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len, + Paint.FontMetricsInt fm) { + return addStyleRun(paint, spans, len, fm, 0 /* native ptr */); + } + int breakText(int limit, boolean forwards, float width) { float[] w = mWidths; if (forwards) { diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java index 5c60188d..c0fc44fd 100644 --- a/android/text/StaticLayout.java +++ b/android/text/StaticLayout.java @@ -32,6 +32,9 @@ import android.util.Pools.SynchronizedPool; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + import java.util.Arrays; /** @@ -57,9 +60,7 @@ public class StaticLayout extends Layout { * default values. */ public final static class Builder { - private Builder() { - mNativePtr = nNewBuilder(); - } + private Builder() {} /** * Obtain a builder for constructing StaticLayout objects. @@ -116,13 +117,11 @@ public class StaticLayout extends Layout { b.mRightIndents = null; b.mLeftPaddings = null; b.mRightPaddings = null; - nFinishBuilder(b.mNativePtr); sPool.release(b); } // release any expensive state /* package */ void finish() { - nFinishBuilder(mNativePtr); mText = null; mPaint = null; mLeftIndents = null; @@ -405,32 +404,6 @@ public class StaticLayout extends Layout { } /** - * Measurement and break iteration is done in native code. The protocol for using - * the native code is as follows. - * - * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab - * stops, break strategy, and hyphenation frequency (and possibly other parameters in the - * future). - * - * Then, for each run within the paragraph: - * - one of the following, depending on the type of run: - * + addStyleRun (a text run, to be measured in native code) - * + addReplacementRun (a replacement run, width is given) - * - * Run nComputeLineBreaks() to obtain line breaks for the paragraph. - * - * After all paragraphs, call finish() to release expensive buffers. - */ - - /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) { - nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl); - } - - /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) { - nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width); - } - - /** * Build the {@link StaticLayout} after options have been set. * * <p>Note: the builder object must not be reused in any way after calling this @@ -446,17 +419,6 @@ public class StaticLayout extends Layout { return result; } - @Override - protected void finalize() throws Throwable { - try { - nFreeBuilder(mNativePtr); - } finally { - super.finalize(); - } - } - - /* package */ long mNativePtr; - private CharSequence mText; private int mStart; private int mEnd; @@ -694,270 +656,294 @@ public class StaticLayout extends Layout { indents = null; } - int paraEnd; - for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) { - paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); - if (paraEnd < 0) - paraEnd = bufEnd; - else - paraEnd++; - - int firstWidthLineCount = 1; - int firstWidth = outerWidth; - int restWidth = outerWidth; - - LineHeightSpan[] chooseHt = null; - - if (spanned != null) { - LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, - LeadingMarginSpan.class); - for (int i = 0; i < sp.length; i++) { - LeadingMarginSpan lms = sp[i]; - firstWidth -= sp[i].getLeadingMargin(true); - restWidth -= sp[i].getLeadingMargin(false); - - // LeadingMarginSpan2 is odd. The count affects all - // leading margin spans, not just this particular one - if (lms instanceof LeadingMarginSpan2) { - LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; - firstWidthLineCount = Math.max(firstWidthLineCount, - lms2.getLeadingMarginLineCount()); - } + final long nativePtr = nInit( + b.mBreakStrategy, b.mHyphenationFrequency, + // TODO: Support more justification mode, e.g. letter spacing, stretching. + b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, + indents, mLeftPaddings, mRightPaddings); + + try { + int paraEnd; + for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd); + if (paraEnd < 0) { + paraEnd = bufEnd; + } else { + paraEnd++; } - chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class); - - if (chooseHt.length == 0) { - chooseHt = null; // So that out() would not assume it has any contents - } else { - if (chooseHtv == null || - chooseHtv.length < chooseHt.length) { - chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length); + int firstWidthLineCount = 1; + int firstWidth = outerWidth; + int restWidth = outerWidth; + + LineHeightSpan[] chooseHt = null; + + if (spanned != null) { + LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, + LeadingMarginSpan.class); + for (int i = 0; i < sp.length; i++) { + LeadingMarginSpan lms = sp[i]; + firstWidth -= sp[i].getLeadingMargin(true); + restWidth -= sp[i].getLeadingMargin(false); + + // LeadingMarginSpan2 is odd. The count affects all + // leading margin spans, not just this particular one + if (lms instanceof LeadingMarginSpan2) { + LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; + firstWidthLineCount = Math.max(firstWidthLineCount, + lms2.getLeadingMarginLineCount()); + } } - for (int i = 0; i < chooseHt.length; i++) { - int o = spanned.getSpanStart(chooseHt[i]); + chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class); - if (o < paraStart) { - // starts in this layout, before the - // current paragraph + if (chooseHt.length == 0) { + chooseHt = null; // So that out() would not assume it has any contents + } else { + if (chooseHtv == null || chooseHtv.length < chooseHt.length) { + chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length); + } - chooseHtv[i] = getLineTop(getLineForOffset(o)); - } else { - // starts in this paragraph + for (int i = 0; i < chooseHt.length; i++) { + int o = spanned.getSpanStart(chooseHt[i]); + + if (o < paraStart) { + // starts in this layout, before the + // current paragraph - chooseHtv[i] = v; + chooseHtv[i] = getLineTop(getLineForOffset(o)); + } else { + // starts in this paragraph + + chooseHtv[i] = v; + } } } } - } - measured.setPara(source, paraStart, paraEnd, textDir, b); - char[] chs = measured.mChars; - float[] widths = measured.mWidths; - byte[] chdirs = measured.mLevels; - int dir = measured.mDir; - boolean easy = measured.mEasy; - - // tab stop locations - int[] variableTabStops = null; - if (spanned != null) { - TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, - paraEnd, TabStopSpan.class); - if (spans.length > 0) { - int[] stops = new int[spans.length]; - for (int i = 0; i < spans.length; i++) { - stops[i] = spans[i].getTabStop(); + measured.setPara(source, paraStart, paraEnd, textDir); + char[] chs = measured.mChars; + float[] widths = measured.mWidths; + byte[] chdirs = measured.mLevels; + int dir = measured.mDir; + boolean easy = measured.mEasy; + + // tab stop locations + int[] variableTabStops = null; + if (spanned != null) { + TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, + paraEnd, TabStopSpan.class); + if (spans.length > 0) { + int[] stops = new int[spans.length]; + for (int i = 0; i < spans.length; i++) { + stops[i] = spans[i].getTabStop(); + } + Arrays.sort(stops, 0, stops.length); + variableTabStops = stops; } - Arrays.sort(stops, 0, stops.length); - variableTabStops = stops; - } - } - - nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart, - firstWidth, firstWidthLineCount, restWidth, - variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency, - // TODO: Support more justification mode, e.g. letter spacing, stretching. - b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, - // TODO: indents and paddings don't need to get passed to native code for every - // paragraph. Pass them to native code just once. - indents, mLeftPaddings, mRightPaddings, mLineCount); - - // measurement has to be done before performing line breaking - // but we don't want to recompute fontmetrics or span ranges the - // second time, so we cache those and then use those stored values - int fmCacheCount = 0; - int spanEndCacheCount = 0; - for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { - if (fmCacheCount * 4 >= fmCache.length) { - int[] grow = new int[fmCacheCount * 4 * 2]; - System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4); - fmCache = grow; - } - - if (spanEndCacheCount >= spanEndCache.length) { - int[] grow = new int[spanEndCacheCount * 2]; - System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); - spanEndCache = grow; - } - - if (spanned == null) { - spanEnd = paraEnd; - int spanLen = spanEnd - spanStart; - measured.addStyleRun(paint, spanLen, fm); - } else { - spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, - MetricAffectingSpan.class); - int spanLen = spanEnd - spanStart; - MetricAffectingSpan[] spans = - spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class); - measured.addStyleRun(paint, spans, spanLen, fm); } - // the order of storage here (top, bottom, ascent, descent) has to match the code below - // where these values are retrieved - fmCache[fmCacheCount * 4 + 0] = fm.top; - fmCache[fmCacheCount * 4 + 1] = fm.bottom; - fmCache[fmCacheCount * 4 + 2] = fm.ascent; - fmCache[fmCacheCount * 4 + 3] = fm.descent; - fmCacheCount++; + // measurement has to be done before performing line breaking + // but we don't want to recompute fontmetrics or span ranges the + // second time, so we cache those and then use those stored values + int fmCacheCount = 0; + int spanEndCacheCount = 0; + for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + if (fmCacheCount * 4 >= fmCache.length) { + int[] grow = new int[fmCacheCount * 4 * 2]; + System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4); + fmCache = grow; + } - spanEndCache[spanEndCacheCount] = spanEnd; - spanEndCacheCount++; - } + if (spanEndCacheCount >= spanEndCache.length) { + int[] grow = new int[spanEndCacheCount * 2]; + System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount); + spanEndCache = grow; + } - int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks, - lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags, - lineBreaks.breaks.length, widths); - - final int[] breaks = lineBreaks.breaks; - final float[] lineWidths = lineBreaks.widths; - final float[] ascents = lineBreaks.ascents; - final float[] descents = lineBreaks.descents; - final int[] flags = lineBreaks.flags; - - final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; - final boolean ellipsisMayBeApplied = ellipsize != null - && (ellipsize == TextUtils.TruncateAt.END - || (mMaximumVisibleLineCount == 1 - && ellipsize != TextUtils.TruncateAt.MARQUEE)); - if (0 < remainingLineCount && remainingLineCount < breakCount - && ellipsisMayBeApplied) { - // Calculate width and flag. - float width = 0; - int flag = 0; // XXX May need to also have starting hyphen edit - for (int i = remainingLineCount - 1; i < breakCount; i++) { - if (i == breakCount - 1) { - width += lineWidths[i]; + if (spanned == null) { + spanEnd = paraEnd; + int spanLen = spanEnd - spanStart; + measured.addStyleRun(paint, spanLen, fm, nativePtr); } else { - for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { - width += widths[j]; - } + spanEnd = spanned.nextSpanTransition(spanStart, paraEnd, + MetricAffectingSpan.class); + int spanLen = spanEnd - spanStart; + MetricAffectingSpan[] spans = + spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, spanned, + MetricAffectingSpan.class); + measured.addStyleRun(paint, spans, spanLen, fm, nativePtr); } - flag |= flags[i] & TAB_MASK; - } - // Treat the last line and overflowed lines as a single line. - breaks[remainingLineCount - 1] = breaks[breakCount - 1]; - lineWidths[remainingLineCount - 1] = width; - flags[remainingLineCount - 1] = flag; - breakCount = remainingLineCount; - } + // the order of storage here (top, bottom, ascent, descent) has to match the + // code below where these values are retrieved + fmCache[fmCacheCount * 4 + 0] = fm.top; + fmCache[fmCacheCount * 4 + 1] = fm.bottom; + fmCache[fmCacheCount * 4 + 2] = fm.ascent; + fmCache[fmCacheCount * 4 + 3] = fm.descent; + fmCacheCount++; - // here is the offset of the starting character of the line we are currently measuring - int here = paraStart; - - int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; - int fmCacheIndex = 0; - int spanEndCacheIndex = 0; - int breakIndex = 0; - for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { - // retrieve end of span - spanEnd = spanEndCache[spanEndCacheIndex++]; - - // retrieve cached metrics, order matches above - fm.top = fmCache[fmCacheIndex * 4 + 0]; - fm.bottom = fmCache[fmCacheIndex * 4 + 1]; - fm.ascent = fmCache[fmCacheIndex * 4 + 2]; - fm.descent = fmCache[fmCacheIndex * 4 + 3]; - fmCacheIndex++; - - if (fm.top < fmTop) { - fmTop = fm.top; - } - if (fm.ascent < fmAscent) { - fmAscent = fm.ascent; - } - if (fm.descent > fmDescent) { - fmDescent = fm.descent; - } - if (fm.bottom > fmBottom) { - fmBottom = fm.bottom; + spanEndCache[spanEndCacheCount] = spanEnd; + spanEndCacheCount++; } - // skip breaks ending before current span range - while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { - breakIndex++; + int breakCount = nComputeLineBreaks( + nativePtr, + + // Inputs + chs, + paraEnd - paraStart, + firstWidth, + firstWidthLineCount, + restWidth, + variableTabStops, + TAB_INCREMENT, + mLineCount, + + // Outputs + lineBreaks, + lineBreaks.breaks.length, + lineBreaks.breaks, + lineBreaks.widths, + lineBreaks.ascents, + lineBreaks.descents, + lineBreaks.flags, + widths); + + final int[] breaks = lineBreaks.breaks; + final float[] lineWidths = lineBreaks.widths; + final float[] ascents = lineBreaks.ascents; + final float[] descents = lineBreaks.descents; + final int[] flags = lineBreaks.flags; + + final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; + final boolean ellipsisMayBeApplied = ellipsize != null + && (ellipsize == TextUtils.TruncateAt.END + || (mMaximumVisibleLineCount == 1 + && ellipsize != TextUtils.TruncateAt.MARQUEE)); + if (0 < remainingLineCount && remainingLineCount < breakCount + && ellipsisMayBeApplied) { + // Calculate width and flag. + float width = 0; + int flag = 0; // XXX May need to also have starting hyphen edit + for (int i = remainingLineCount - 1; i < breakCount; i++) { + if (i == breakCount - 1) { + width += lineWidths[i]; + } else { + for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { + width += widths[j]; + } + } + flag |= flags[i] & TAB_MASK; + } + // Treat the last line and overflowed lines as a single line. + breaks[remainingLineCount - 1] = breaks[breakCount - 1]; + lineWidths[remainingLineCount - 1] = width; + flags[remainingLineCount - 1] = flag; + + breakCount = remainingLineCount; } - while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { - int endPos = paraStart + breaks[breakIndex]; - - boolean moreChars = (endPos < bufEnd); - - final int ascent = fallbackLineSpacing - ? Math.min(fmAscent, Math.round(ascents[breakIndex])) - : fmAscent; - final int descent = fallbackLineSpacing - ? Math.max(fmDescent, Math.round(descents[breakIndex])) - : fmDescent; - v = out(source, here, endPos, - ascent, descent, fmTop, fmBottom, - v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex], - needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, - addLastLineSpacing, chs, widths, paraStart, ellipsize, - ellipsizedWidth, lineWidths[breakIndex], paint, moreChars); - - if (endPos < spanEnd) { - // preserve metrics for current span + // here is the offset of the starting character of the line we are currently + // measuring + int here = paraStart; + + int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; + int fmCacheIndex = 0; + int spanEndCacheIndex = 0; + int breakIndex = 0; + for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + // retrieve end of span + spanEnd = spanEndCache[spanEndCacheIndex++]; + + // retrieve cached metrics, order matches above + fm.top = fmCache[fmCacheIndex * 4 + 0]; + fm.bottom = fmCache[fmCacheIndex * 4 + 1]; + fm.ascent = fmCache[fmCacheIndex * 4 + 2]; + fm.descent = fmCache[fmCacheIndex * 4 + 3]; + fmCacheIndex++; + + if (fm.top < fmTop) { fmTop = fm.top; - fmBottom = fm.bottom; + } + if (fm.ascent < fmAscent) { fmAscent = fm.ascent; + } + if (fm.descent > fmDescent) { fmDescent = fm.descent; - } else { - fmTop = fmBottom = fmAscent = fmDescent = 0; } + if (fm.bottom > fmBottom) { + fmBottom = fm.bottom; + } + + // skip breaks ending before current span range + while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { + breakIndex++; + } + + while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { + int endPos = paraStart + breaks[breakIndex]; + + boolean moreChars = (endPos < bufEnd); + + final int ascent = fallbackLineSpacing + ? Math.min(fmAscent, Math.round(ascents[breakIndex])) + : fmAscent; + final int descent = fallbackLineSpacing + ? Math.max(fmDescent, Math.round(descents[breakIndex])) + : fmDescent; + v = out(source, here, endPos, + ascent, descent, fmTop, fmBottom, + v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, + flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd, + includepad, trackpad, addLastLineSpacing, chs, widths, paraStart, + ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, + moreChars); + + if (endPos < spanEnd) { + // preserve metrics for current span + fmTop = fm.top; + fmBottom = fm.bottom; + fmAscent = fm.ascent; + fmDescent = fm.descent; + } else { + fmTop = fmBottom = fmAscent = fmDescent = 0; + } - here = endPos; - breakIndex++; + here = endPos; + breakIndex++; - if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) { - return; + if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) { + return; + } } } - } - if (paraEnd == bufEnd) - break; - } + if (paraEnd == bufEnd) { + break; + } + } - if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && - mLineCount < mMaximumVisibleLineCount) { - measured.setPara(source, bufEnd, bufEnd, textDir, b); - - paint.getFontMetricsInt(fm); - - v = out(source, - bufEnd, bufEnd, fm.ascent, fm.descent, - fm.top, fm.bottom, - v, - spacingmult, spacingadd, null, - null, fm, 0, - needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, - includepad, trackpad, addLastLineSpacing, null, - null, bufStart, ellipsize, - ellipsizedWidth, 0, paint, false); + if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) + && mLineCount < mMaximumVisibleLineCount) { + measured.setPara(source, bufEnd, bufEnd, textDir); + + paint.getFontMetricsInt(fm); + + v = out(source, + bufEnd, bufEnd, fm.ascent, fm.descent, + fm.top, fm.bottom, + v, + spacingmult, spacingadd, null, + null, fm, 0, + needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd, + includepad, trackpad, addLastLineSpacing, null, + null, bufStart, ellipsize, + ellipsizedWidth, 0, paint, false); + } + } finally { + nFinish(nativePtr); } } @@ -1487,26 +1473,51 @@ public class StaticLayout extends Layout { mMaxLineHeight : super.getHeight(); } - private static native long nNewBuilder(); - private static native void nFreeBuilder(long nativePtr); - private static native void nFinishBuilder(long nativePtr); - - // Set up paragraph text and settings; done as one big method to minimize jni crossings - private static native void nSetupParagraph( - /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length, - @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount, - @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops, - int defaultTabStop, @BreakStrategy int breakStrategy, - @HyphenationFrequency int hyphenationFrequency, boolean isJustified, - @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings, - @IntRange(from = 0) int indentsOffset); - - // TODO: Make this method CriticalNative once native code defers doing layouts. + /** + * Measurement and break iteration is done in native code. The protocol for using + * the native code is as follows. + * + * First, call nInit to setup native line breaker object. Then, for each paragraph, do the + * following: + * + * - Call one of the following methods for each run within the paragraph depending on the type + * of run: + * + addStyleRun (a text run, to be measured in native code) + * + addReplacementRun (a replacement run, width is given) + * + * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. + * + * After all paragraphs, call finish() to release expensive buffers. + */ + + /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end, + boolean isRtl) { + nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl); + } + + /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end, + float width) { + nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width); + } + + @FastNative + private static native long nInit( + @BreakStrategy int breakStrategy, + @HyphenationFrequency int hyphenationFrequency, + boolean isJustified, + @Nullable int[] indents, + @Nullable int[] leftPaddings, + @Nullable int[] rightPaddings); + + @CriticalNative + private static native void nFinish(long nativePtr); + + @CriticalNative private static native void nAddStyleRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); - // TODO: Make this method CriticalNative once native code defers doing layouts. + @CriticalNative private static native void nAddReplacementRun( /* non-zero */ long nativePtr, /* non-zero */ long nativePaint, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @@ -1519,10 +1530,28 @@ public class StaticLayout extends Layout { // arrays do not have to be resized // The individual character widths will be returned in charWidths. The length of charWidths must // be at least the length of the text. - private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle, - int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents, - float[] recycleDescents, int[] recycleFlags, int recycleLength, - float[] charWidths); + private static native int nComputeLineBreaks( + /* non zero */ long nativePtr, + + // Inputs + @NonNull char[] text, + @IntRange(from = 0) int length, + @FloatRange(from = 0.0f) float firstWidth, + @IntRange(from = 0) int firstWidthLineCount, + @FloatRange(from = 0.0f) float restWidth, + @Nullable int[] variableTabStops, + int defaultTabStop, + @IntRange(from = 0) int indentsOffset, + + // Outputs + @NonNull LineBreaks recycle, + @IntRange(from = 0) int recycleLength, + @NonNull int[] recycleBreaks, + @NonNull float[] recycleWidths, + @NonNull float[] recycleAscents, + @NonNull float[] recycleDescents, + @NonNull int[] recycleFlags, + @NonNull float[] charWidths); private int mLineCount; private int mTopPadding, mBottomPadding; diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java index def3c91c..ef7525ec 100644 --- a/android/text/StaticLayout_Delegate.java +++ b/android/text/StaticLayout_Delegate.java @@ -4,12 +4,14 @@ import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.BidiRenderer; import android.graphics.Paint; import android.graphics.Paint_Delegate; import android.graphics.RectF; import android.icu.text.BreakIterator; -import android.icu.util.ULocale; +import android.text.Layout.BreakStrategy; +import android.text.Layout.HyphenationFrequency; import android.text.Primitive.PrimitiveType; import android.text.StaticLayout.LineBreaks; @@ -38,35 +40,21 @@ public class StaticLayout_Delegate { new DelegateManager<Builder>(Builder.class); @LayoutlibDelegate - /*package*/ static long nNewBuilder() { - return sBuilderManager.addNewDelegate(new Builder()); + /*package*/ static long nInit( + @BreakStrategy int breakStrategy, + @HyphenationFrequency int hyphenationFrequency, + boolean isJustified, + @Nullable int[] indents, + @Nullable int[] leftPaddings, + @Nullable int[] rightPaddings) { + Builder builder = new Builder(); + builder.mBreakStrategy = breakStrategy; + return sBuilderManager.addNewDelegate(builder); } @LayoutlibDelegate - /*package*/ static void nFreeBuilder(long nativeBuilder) { - sBuilderManager.removeJavaReferenceFor(nativeBuilder); - } - - @LayoutlibDelegate - /*package*/ static void nFinishBuilder(long nativeBuilder) { - } - - @LayoutlibDelegate - /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length, - float firstWidth, int firstWidthLineCount, float restWidth, - int[] variableTabStops, int defaultTabStop, int breakStrategy, - int hyphenationFrequency, boolean isJustified, int[] indents, int[] leftPaddings, - int[] rightPaddings, int intentsOffset) { - // TODO: implement justified alignment - Builder builder = sBuilderManager.getDelegate(nativeBuilder); - if (builder == null) { - return; - } - - builder.mText = text; - builder.mWidths = new float[length]; - builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth); - builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop); + /*package*/ static void nFinish(long nativePtr) { + sBuilderManager.removeJavaReferenceFor(nativePtr); } @LayoutlibDelegate @@ -76,10 +64,7 @@ public class StaticLayout_Delegate { if (builder == null) { return; } - - int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR; - measureText(nativePaint, builder.mText, start, end - start, builder.mWidths, - bidiFlags); + builder.mRuns.add(new StyleRun(nativePaint, start, end, isRtl)); } @LayoutlibDelegate @@ -89,22 +74,47 @@ public class StaticLayout_Delegate { if (builder == null) { return; } - builder.mWidths[start] = width; - Arrays.fill(builder.mWidths, start + 1, end, 0.0f); + builder.mRuns.add(new ReplacementRun(start, end, width)); } @LayoutlibDelegate - /*package*/ static int nComputeLineBreaks(long nativeBuilder, LineBreaks recycle, - int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents, - float[] recycleDescents, int[] recycleFlags, int recycleLength, float[] charWidths) { + /*package*/ static int nComputeLineBreaks( + /* non zero */ long nativePtr, - Builder builder = sBuilderManager.getDelegate(nativeBuilder); + // Inputs + @NonNull char[] text, + int length, + float firstWidth, + int firstWidthLineCount, + float restWidth, + @Nullable int[] variableTabStops, + int defaultTabStop, + int indentsOffset, + + // Outputs + @NonNull LineBreaks recycle, + int recycleLength, + @NonNull int[] recycleBreaks, + @NonNull float[] recycleWidths, + @NonNull float[] recycleAscents, + @NonNull float[] recycleDescents, + @NonNull int[] recycleFlags, + @NonNull float[] charWidths) { + Builder builder = sBuilderManager.getDelegate(nativePtr); if (builder == null) { return 0; } + builder.mText = text; + builder.mWidths = new float[length]; + builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth); + builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop); + + for (Run run: builder.mRuns) { + run.addTo(builder); + } + // compute all possible breakpoints. - int length = builder.mWidths.length; BreakIterator it = BreakIterator.getLineInstance(); it.setText(new Segment(builder.mText, 0, length)); @@ -196,9 +206,55 @@ public class StaticLayout_Delegate { private static class Builder { char[] mText; float[] mWidths; - LineBreaker mLineBreaker; - int mBreakStrategy; - LineWidth mLineWidth; - TabStops mTabStopCalculator; + private LineBreaker mLineBreaker; + private int mBreakStrategy; + private LineWidth mLineWidth; + private TabStops mTabStopCalculator; + private ArrayList<Run> mRuns = new ArrayList<>(); + } + + private abstract static class Run { + int mStart; + int mEnd; + + Run(int start, int end) { + mStart = start; + mEnd = end; + } + + abstract void addTo(Builder builder); + } + + private static class StyleRun extends Run { + private long mNativePaint; + private boolean mIsRtl; + + private StyleRun(long nativePaint, int start, int end, boolean isRtl) { + super(start, end); + mNativePaint = nativePaint; + mIsRtl = isRtl; + } + + @Override + void addTo(Builder builder) { + int bidiFlags = mIsRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR; + measureText(mNativePaint, builder.mText, mStart, mEnd - mStart, builder.mWidths, + bidiFlags); + } + } + + private static class ReplacementRun extends Run { + private final float mWidth; + + private ReplacementRun(int start, int end, float width) { + super(start, end); + mWidth = width; + } + + @Override + void addTo(Builder builder) { + builder.mWidths[mStart] = mWidth; + Arrays.fill(builder.mWidths, mStart + 1, mEnd, 0.0f); + } } } diff --git a/android/text/TextLine.java b/android/text/TextLine.java index 20c0ed87..86cc0141 100644 --- a/android/text/TextLine.java +++ b/android/text/TextLine.java @@ -28,6 +28,7 @@ import android.text.style.MetricAffectingSpan; import android.text.style.ReplacementSpan; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; @@ -44,7 +45,8 @@ import java.util.ArrayList; * * @hide */ -class TextLine { +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +public class TextLine { private static final boolean DEBUG = false; private TextPaint mPaint; @@ -82,7 +84,8 @@ class TextLine { * * @return an uninitialized TextLine */ - static TextLine obtain() { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static TextLine obtain() { TextLine tl; synchronized (sCached) { for (int i = sCached.length; --i >= 0;) { @@ -107,7 +110,8 @@ class TextLine { * @return null, as a convenience from clearing references to the provided * TextLine */ - static TextLine recycle(TextLine tl) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static TextLine recycle(TextLine tl) { tl.mText = null; tl.mPaint = null; tl.mDirections = null; @@ -142,7 +146,8 @@ class TextLine { * @param hasTabs true if the line might contain tabs * @param tabStops the tabStops. Can be null. */ - void set(TextPaint paint, CharSequence text, int start, int limit, int dir, + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void set(TextPaint paint, CharSequence text, int start, int limit, int dir, Directions directions, boolean hasTabs, TabStops tabStops) { mPaint = paint; mText = text; @@ -196,7 +201,8 @@ class TextLine { /** * Justify the line to the given width. */ - void justify(float justifyWidth) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void justify(float justifyWidth) { int end = mLen; while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) { end--; @@ -277,7 +283,8 @@ class TextLine { * @param fmi receives font metrics information, can be null * @return the signed width of the line */ - float metrics(FontMetricsInt fmi) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public float metrics(FontMetricsInt fmi) { return measure(mLen, false, fmi); } @@ -1165,23 +1172,18 @@ class TextLine { } private boolean isStretchableWhitespace(int ch) { - // TODO: Support other stretchable whitespace. (Bug: 34013491) - return ch == 0x0020 || ch == 0x00A0; - } - - private int nextStretchableSpace(int start, int end) { - for (int i = start; i < end; i++) { - final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart); - if (isStretchableWhitespace(c)) return i; - } - return end; + // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709). + return ch == 0x0020; } /* Return the number of spaces in the text line, for the purpose of justification */ private int countStretchableSpaces(int start, int end) { int count = 0; - for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) { - count++; + for (int i = start; i < end; i++) { + final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart); + if (isStretchableWhitespace(c)) { + count++; + } } return count; } diff --git a/android/text/TextUtils.java b/android/text/TextUtils.java index 68afeecf..cbdaa69b 100644 --- a/android/text/TextUtils.java +++ b/android/text/TextUtils.java @@ -1519,7 +1519,7 @@ public class TextUtils { } // XXX this is probably ok, but need to look at it more - tempMt.setPara(format, 0, format.length(), textDir, null); + tempMt.setPara(format, 0, format.length(), textDir); float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null); if (w + moreWid <= avail) { @@ -1541,7 +1541,7 @@ public class TextUtils { private static float setPara(MeasuredText mt, TextPaint paint, CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - mt.setPara(text, start, end, textDir, null); + mt.setPara(text, start, end, textDir); float width; Spanned sp = text instanceof Spanned ? (Spanned) text : null; diff --git a/android/util/Log.java b/android/util/Log.java index b94e48b3..02998653 100644 --- a/android/util/Log.java +++ b/android/util/Log.java @@ -16,12 +16,45 @@ package android.util; +import android.os.DeadSystemException; + +import com.android.internal.os.RuntimeInit; +import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.LineBreakBufferedWriter; + import java.io.PrintWriter; import java.io.StringWriter; +import java.io.Writer; import java.net.UnknownHostException; /** - * Mock Log implementation for testing on non android host. + * API for sending log output. + * + * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()}, + * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs. + * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>. + * + * <p>The order in terms of verbosity, from least to most is + * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled + * into an application except during development. Debug logs are compiled + * in but stripped at runtime. Error, warning and info logs are always kept. + * + * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant + * in your class: + * + * <pre>private static final String TAG = "MyActivity";</pre> + * + * and use that in subsequent calls to the log methods. + * </p> + * + * <p><b>Tip:</b> Don't forget that when you make a call like + * <pre>Log.v(TAG, "index=" + i);</pre> + * that when you're building the string to pass into Log.d, the compiler uses a + * StringBuilder and at least three allocations occur: the StringBuilder + * itself, the buffer, and the String object. Realistically, there is also + * another buffer allocation and copy, and even more pressure on the gc. + * That means that if your log message is filtered out, you might be doing + * significant work and incurring significant overhead. */ public final class Log { @@ -55,6 +88,29 @@ public final class Log { */ public static final int ASSERT = 7; + /** + * Exception class used to capture a stack trace in {@link #wtf}. + * @hide + */ + public static class TerribleFailure extends Exception { + TerribleFailure(String msg, Throwable cause) { super(msg, cause); } + } + + /** + * Interface to handle terrible failures from {@link #wtf}. + * + * @hide + */ + public interface TerribleFailureHandler { + void onTerribleFailure(String tag, TerribleFailure what, boolean system); + } + + private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() { + public void onTerribleFailure(String tag, TerribleFailure what, boolean system) { + RuntimeInit.wtf(tag, what, system); + } + }; + private Log() { } @@ -65,7 +121,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int v(String tag, String msg) { - return println(LOG_ID_MAIN, VERBOSE, tag, msg); + return println_native(LOG_ID_MAIN, VERBOSE, tag, msg); } /** @@ -76,7 +132,7 @@ public final class Log { * @param tr An exception to log */ public static int v(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr); } /** @@ -86,7 +142,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int d(String tag, String msg) { - return println(LOG_ID_MAIN, DEBUG, tag, msg); + return println_native(LOG_ID_MAIN, DEBUG, tag, msg); } /** @@ -97,7 +153,7 @@ public final class Log { * @param tr An exception to log */ public static int d(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr); } /** @@ -107,7 +163,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int i(String tag, String msg) { - return println(LOG_ID_MAIN, INFO, tag, msg); + return println_native(LOG_ID_MAIN, INFO, tag, msg); } /** @@ -118,7 +174,7 @@ public final class Log { * @param tr An exception to log */ public static int i(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, INFO, tag, msg, tr); } /** @@ -128,7 +184,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int w(String tag, String msg) { - return println(LOG_ID_MAIN, WARN, tag, msg); + return println_native(LOG_ID_MAIN, WARN, tag, msg); } /** @@ -139,9 +195,31 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, WARN, tag, msg, tr); } + /** + * Checks to see whether or not a log for the specified tag is loggable at the specified level. + * + * The default level of any tag is set to INFO. This means that any level above and including + * INFO will be logged. Before you make any calls to a logging method you should check to see + * if your tag should be logged. You can change the default level by setting a system property: + * 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>' + * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will + * turn off all logging for your tag. You can also create a local.prop file that with the + * following in it: + * 'log.tag.<YOUR_LOG_TAG>=<LEVEL>' + * and place that in /data/local.prop. + * + * @param tag The tag to check. + * @param level The level to check. + * @return Whether or not that this is allowed to be logged. + * @throws IllegalArgumentException is thrown if the tag.length() > 23 + * for Nougat (7.0) releases (API <= 23) and prior, there is no + * tag limit of concern after this API level. + */ + public static native boolean isLoggable(String tag, int level); + /* * Send a {@link #WARN} log message and log the exception. * @param tag Used to identify the source of a log message. It usually identifies @@ -149,7 +227,7 @@ public final class Log { * @param tr An exception to log */ public static int w(String tag, Throwable tr) { - return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, WARN, tag, "", tr); } /** @@ -159,7 +237,7 @@ public final class Log { * @param msg The message you would like logged. */ public static int e(String tag, String msg) { - return println(LOG_ID_MAIN, ERROR, tag, msg); + return println_native(LOG_ID_MAIN, ERROR, tag, msg); } /** @@ -170,7 +248,82 @@ public final class Log { * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { - return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr)); + return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr); + } + + /** + * What a Terrible Failure: Report a condition that should never happen. + * The error will always be logged at level ASSERT with the call stack. + * Depending on system configuration, a report may be added to the + * {@link android.os.DropBoxManager} and/or the process may be terminated + * immediately with an error dialog. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + */ + public static int wtf(String tag, String msg) { + return wtf(LOG_ID_MAIN, tag, msg, null, false, false); + } + + /** + * Like {@link #wtf(String, String)}, but also writes to the log the full + * call stack. + * @hide + */ + public static int wtfStack(String tag, String msg) { + return wtf(LOG_ID_MAIN, tag, msg, null, true, false); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, String)}, with an exception to log. + * @param tag Used to identify the source of a log message. + * @param tr An exception to log. + */ + public static int wtf(String tag, Throwable tr) { + return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, Throwable)}, with a message as well. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + * @param tr An exception to log. May be null. + */ + public static int wtf(String tag, String msg, Throwable tr) { + return wtf(LOG_ID_MAIN, tag, msg, tr, false, false); + } + + static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack, + boolean system) { + TerribleFailure what = new TerribleFailure(msg, tr); + // Only mark this as ERROR, do not use ASSERT since that should be + // reserved for cases where the system is guaranteed to abort. + // The onTerribleFailure call does not always cause a crash. + int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr); + sWtfHandler.onTerribleFailure(tag, what, system); + return bytes; + } + + static void wtfQuiet(int logId, String tag, String msg, boolean system) { + TerribleFailure what = new TerribleFailure(msg, null); + sWtfHandler.onTerribleFailure(tag, what, system); + } + + /** + * Sets the terrible failure handler, for testing. + * + * @return the old handler + * + * @hide + */ + public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) { + if (handler == null) { + throw new NullPointerException("handler == null"); + } + TerribleFailureHandler oldHandler = sWtfHandler; + sWtfHandler = handler; + return oldHandler; } /** @@ -193,7 +346,7 @@ public final class Log { } StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); + PrintWriter pw = new FastPrintWriter(sw, false, 256); tr.printStackTrace(pw); pw.flush(); return sw.toString(); @@ -208,7 +361,7 @@ public final class Log { * @return The number of bytes written. */ public static int println(int priority, String tag, String msg) { - return println(LOG_ID_MAIN, priority, tag, msg); + return println_native(LOG_ID_MAIN, priority, tag, msg); } /** @hide */ public static final int LOG_ID_MAIN = 0; @@ -217,9 +370,115 @@ public final class Log { /** @hide */ public static final int LOG_ID_SYSTEM = 3; /** @hide */ public static final int LOG_ID_CRASH = 4; - /** @hide */ @SuppressWarnings("unused") - public static int println(int bufID, - int priority, String tag, String msg) { - return 0; + /** @hide */ public static native int println_native(int bufID, + int priority, String tag, String msg); + + /** + * Return the maximum payload the log daemon accepts without truncation. + * @return LOGGER_ENTRY_MAX_PAYLOAD. + */ + private static native int logger_entry_max_payload_native(); + + /** + * Helper function for long messages. Uses the LineBreakBufferedWriter to break + * up long messages and stacktraces along newlines, but tries to write in large + * chunks. This is to avoid truncation. + * @hide + */ + public static int printlns(int bufID, int priority, String tag, String msg, + Throwable tr) { + ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag); + // Acceptable buffer size. Get the native buffer size, subtract two zero terminators, + // and the length of the tag. + // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It + // is too expensive to compute that ahead of time. + int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base. + - 2 // Two terminators. + - (tag != null ? tag.length() : 0) // Tag length. + - 32; // Some slack. + // At least assume you can print *some* characters (tag is not too large). + bufferSize = Math.max(bufferSize, 100); + + LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize); + + lbbw.println(msg); + + if (tr != null) { + // This is to reduce the amount of log spew that apps do in the non-error + // condition of the network being unavailable. + Throwable t = tr; + while (t != null) { + if (t instanceof UnknownHostException) { + break; + } + if (t instanceof DeadSystemException) { + lbbw.println("DeadSystemException: The system died; " + + "earlier logs will point to the root cause"); + break; + } + t = t.getCause(); + } + if (t == null) { + tr.printStackTrace(lbbw); + } + } + + lbbw.flush(); + + return logWriter.getWritten(); + } + + /** + * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid + * a JNI call during logging. + */ + static class PreloadHolder { + public final static int LOGGER_ENTRY_MAX_PAYLOAD = + logger_entry_max_payload_native(); + } + + /** + * Helper class to write to the logcat. Different from LogWriter, this writes + * the whole given buffer and does not break along newlines. + */ + private static class ImmediateLogWriter extends Writer { + + private int bufID; + private int priority; + private String tag; + + private int written = 0; + + /** + * Create a writer that immediately writes to the log, using the given + * parameters. + */ + public ImmediateLogWriter(int bufID, int priority, String tag) { + this.bufID = bufID; + this.priority = priority; + this.tag = tag; + } + + public int getWritten() { + return written; + } + + @Override + public void write(char[] cbuf, int off, int len) { + // Note: using String here has a bit of overhead as a Java object is created, + // but using the char[] directly is not easier, as it needs to be translated + // to a C char[] for logging. + written += println_native(bufID, priority, tag, new String(cbuf, off, len)); + } + + @Override + public void flush() { + // Ignored. + } + + @Override + public void close() { + // Ignored. + } } } diff --git a/android/util/LruCache.java b/android/util/LruCache.java index 52086065..40154880 100644 --- a/android/util/LruCache.java +++ b/android/util/LruCache.java @@ -20,10 +20,6 @@ import java.util.LinkedHashMap; import java.util.Map; /** - * BEGIN LAYOUTLIB CHANGE - * This is a custom version that doesn't use the non standard LinkedHashMap#eldest. - * END LAYOUTLIB CHANGE - * * A cache that holds strong references to a limited number of values. Each time * a value is accessed, it is moved to the head of a queue. When a value is * added to a full cache, the value at the end of that queue is evicted and may @@ -91,9 +87,8 @@ public class LruCache<K, V> { /** * Sets the size of the cache. - * @param maxSize The new maximum size. * - * @hide + * @param maxSize The new maximum size. */ public void resize(int maxSize) { if (maxSize <= 0) { @@ -190,10 +185,13 @@ public class LruCache<K, V> { } /** + * Remove the eldest entries until the total of remaining entries is at or + * below the requested size. + * * @param maxSize the maximum size of the cache before returning. May be -1 - * to evict even 0-sized elements. + * to evict even 0-sized elements. */ - private void trimToSize(int maxSize) { + public void trimToSize(int maxSize) { while (true) { K key; V value; @@ -207,16 +205,7 @@ public class LruCache<K, V> { break; } - // BEGIN LAYOUTLIB CHANGE - // get the last item in the linked list. - // This is not efficient, the goal here is to minimize the changes - // compared to the platform version. - Map.Entry<K, V> toEvict = null; - for (Map.Entry<K, V> entry : map.entrySet()) { - toEvict = entry; - } - // END LAYOUTLIB CHANGE - + Map.Entry<K, V> toEvict = map.eldest(); if (toEvict == null) { break; } diff --git a/android/view/RectShadowPainter.java b/android/view/RectShadowPainter.java index 5665d4f3..88771a76 100644 --- a/android/view/RectShadowPainter.java +++ b/android/view/RectShadowPainter.java @@ -19,8 +19,10 @@ package android.view; import com.android.layoutlib.bridge.impl.GcSnapshot; import com.android.layoutlib.bridge.impl.ResourceHelper; +import android.graphics.BaseCanvas_Delegate; import android.graphics.Canvas; import android.graphics.Canvas_Delegate; +import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Outline; import android.graphics.Paint; @@ -46,7 +48,8 @@ public class RectShadowPainter { private static final int END_COLOR = ResourceHelper.getColor("#03000000"); private static final float PERPENDICULAR_ANGLE = 90f; - public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) { + public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas, + float alpha) { Rect outline = new Rect(); if (!viewOutline.getRect(outline)) { assert false : "Outline is not a rect shadow"; @@ -74,9 +77,16 @@ public class RectShadowPainter { edgePaint.setAntiAlias(false); float outerArcRadius = radius + shadowSize; int[] colors = {START_COLOR, START_COLOR, END_COLOR}; + if (alpha != 1f) { + // Correct colors using the given component alpha + for (int i = 0; i < colors.length; i++) { + colors[i] = Color.argb((int) (Color.alpha(colors[i]) * alpha), Color.red(colors[i]), + Color.green(colors[i]), Color.blue(colors[i])); + } + } cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors, new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP)); - edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, START_COLOR, END_COLOR, + edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, colors[0], colors[2], TileMode.CLAMP)); Path path = new Path(); path.setFillType(FillType.EVEN_ODD); @@ -184,7 +194,8 @@ public class RectShadowPainter { /** * Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks. * <p/> - * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float, + * This is required because {@link BaseCanvas_Delegate#native_drawRect(long, float, float, + * float, * float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up * drawing empty rectangles, which results in IllegalArgumentException. */ diff --git a/android/view/ShadowPainter.java b/android/view/ShadowPainter.java index f09fffd1..788c6c35 100644 --- a/android/view/ShadowPainter.java +++ b/android/view/ShadowPainter.java @@ -41,14 +41,16 @@ public class ShadowPainter { * @param source the source image * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link * #SMALL_SHADOW_SIZE}} + * @param alpha alpha value to apply to the shadow * * @return an image with the shadow painted in or the source image if shadowSize <= 1 */ @NonNull - public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) { + public static BufferedImage createDropShadow(BufferedImage source, int shadowSize, float + alpha) { shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class - return createDropShadow(source, shadowSize, 0.7f, 0); + return createDropShadow(source, shadowSize, 0.7f * alpha, 0); } /** diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java index ff027a94..6f8315ae 100644 --- a/android/view/SurfaceControl.java +++ b/android/view/SurfaceControl.java @@ -16,27 +16,22 @@ package android.view; -import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; - import android.annotation.Size; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; -import android.graphics.Point; -import android.graphics.PointF; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; -import android.os.Binder; -import android.os.Debug; import android.os.IBinder; +import android.os.Process; +import android.os.UserHandle; import android.util.Log; import android.view.Surface.OutOfResourcesException; - import dalvik.system.CloseGuard; +import libcore.util.NativeAllocationRegistry; import java.io.Closeable; -import libcore.util.NativeAllocationRegistry; - /** * SurfaceControl * @hide @@ -60,6 +55,8 @@ public class SurfaceControl { private static native void nativeScreenshot(IBinder displayToken, Surface consumer, Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean allLayers, boolean useIdentityTransform); + private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer, + int rotation); private static native long nativeCreateTransaction(); private static native long nativeGetNativeTransactionFinalizer(); @@ -186,7 +183,7 @@ public class SurfaceControl { /** * Surface creation flag: Indicates that the surface must be considered opaque, - * even if its pixel format is set to translucent. This can be useful if an + * even if its pixel format contains an alpha channel. This can be useful if an * application needs full RGBA 8888 support for instance but will * still draw every pixel opaque. * <p> @@ -307,6 +304,203 @@ public class SurfaceControl { public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731; /** + * Builder class for {@link SurfaceControl} objects. + */ + public static class Builder { + private SurfaceSession mSession; + private int mFlags = HIDDEN; + private int mWidth; + private int mHeight; + private int mFormat = PixelFormat.OPAQUE; + private String mName; + private SurfaceControl mParent; + private int mWindowType; + private int mOwnerUid; + + /** + * Begin building a SurfaceControl with a given {@link SurfaceSession}. + * + * @param session The {@link SurfaceSession} with which to eventually construct the surface. + */ + public Builder(SurfaceSession session) { + mSession = session; + } + + /** + * Construct a new {@link SurfaceControl} with the set parameters. + */ + public SurfaceControl build() { + if (mWidth <= 0 || mHeight <= 0) { + throw new IllegalArgumentException( + "width and height must be set"); + } + return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat, + mFlags, mParent, mWindowType, mOwnerUid); + } + + /** + * Set a debugging-name for the SurfaceControl. + * + * @param name A name to identify the Surface in debugging. + */ + public Builder setName(String name) { + mName = name; + return this; + } + + /** + * Set the initial size of the controlled surface's buffers in pixels. + * + * @param width The buffer width in pixels. + * @param height The buffer height in pixels. + */ + public Builder setSize(int width, int height) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException( + "width and height must be positive"); + } + mWidth = width; + mHeight = height; + return this; + } + + /** + * Set the pixel format of the controlled surface's buffers, using constants from + * {@link android.graphics.PixelFormat}. + */ + public Builder setFormat(@PixelFormat.Format int format) { + mFormat = format; + return this; + } + + /** + * Specify if the app requires a hardware-protected path to + * an external display sync. If protected content is enabled, but + * such a path is not available, then the controlled Surface will + * not be displayed. + * + * @param protectedContent Whether to require a protected sink. + */ + public Builder setProtected(boolean protectedContent) { + if (protectedContent) { + mFlags |= PROTECTED_APP; + } else { + mFlags &= ~PROTECTED_APP; + } + return this; + } + + /** + * Specify whether the Surface contains secure content. If true, the system + * will prevent the surfaces content from being copied by another process. In + * particular screenshots and VNC servers will be disabled. This is however + * not a complete prevention of readback as {@link #setProtected}. + */ + public Builder setSecure(boolean secure) { + if (secure) { + mFlags |= SECURE; + } else { + mFlags &= ~SECURE; + } + return this; + } + + /** + * Indicates whether the surface must be considered opaque, + * even if its pixel format is set to translucent. This can be useful if an + * application needs full RGBA 8888 support for instance but will + * still draw every pixel opaque. + * <p> + * This flag only determines whether opacity will be sampled from the alpha channel. + * Plane-alpha from calls to setAlpha() can still result in blended composition + * regardless of the opaque setting. + * + * Combined effects are (assuming a buffer format with an alpha channel): + * <ul> + * <li>OPAQUE + alpha(1.0) == opaque composition + * <li>OPAQUE + alpha(0.x) == blended composition + * <li>OPAQUE + alpha(0.0) == no composition + * <li>!OPAQUE + alpha(1.0) == blended composition + * <li>!OPAQUE + alpha(0.x) == blended composition + * <li>!OPAQUE + alpha(0.0) == no composition + * </ul> + * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true) + * were set automatically. + * @param opaque Whether the Surface is OPAQUE. + */ + public Builder setOpaque(boolean opaque) { + if (opaque) { + mFlags |= OPAQUE; + } else { + mFlags &= ~OPAQUE; + } + return this; + } + + /** + * Set a parent surface for our new SurfaceControl. + * + * Child surfaces are constrained to the onscreen region of their parent. + * Furthermore they stack relatively in Z order, and inherit the transformation + * of the parent. + * + * @param parent The parent control. + */ + public Builder setParent(SurfaceControl parent) { + mParent = parent; + return this; + } + + /** + * Set surface metadata. + * + * Currently these are window-types as per {@link WindowManager.LayoutParams} and + * owner UIDs. Child surfaces inherit their parents + * metadata so only the WindowManager needs to set this on root Surfaces. + * + * @param windowType A window-type + * @param ownerUid UID of the window owner. + */ + public Builder setMetadata(int windowType, int ownerUid) { + if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) { + throw new UnsupportedOperationException( + "It only makes sense to set Surface metadata from the WindowManager"); + } + mWindowType = windowType; + mOwnerUid = ownerUid; + return this; + } + + /** + * Indicate whether a 'ColorLayer' is to be constructed. + * + * Color layers will not have an associated BufferQueue and will instead always render a + * solid color (that is, solid before plane alpha). Currently that color is black. + * + * @param isColorLayer Whether to create a color layer. + */ + public Builder setColorLayer(boolean isColorLayer) { + if (isColorLayer) { + mFlags |= FX_SURFACE_DIM; + } else { + mFlags &= ~FX_SURFACE_DIM; + } + return this; + } + + /** + * Set 'Surface creation flags' such as {@link HIDDEN}, {@link SECURE}. + * + * TODO: Finish conversion to individual builder methods? + * @param flags The combined flags + */ + public Builder setFlags(int flags) { + mFlags = flags; + return this; + } + } + + /** * Create a surface with a name. * <p> * The surface creation flags specify what kind of surface to create and @@ -331,19 +525,7 @@ public class SurfaceControl { * * @throws throws OutOfResourcesException If the SurfaceControl cannot be created. */ - public SurfaceControl(SurfaceSession session, - String name, int w, int h, int format, int flags, int windowType, int ownerUid) - throws OutOfResourcesException { - this(session, name, w, h, format, flags, null, windowType, ownerUid); - } - - public SurfaceControl(SurfaceSession session, - String name, int w, int h, int format, int flags) - throws OutOfResourcesException { - this(session, name, w, h, format, flags, null, INVALID_WINDOW_TYPE, Binder.getCallingUid()); - } - - public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, + private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, SurfaceControl parent, int windowType, int ownerUid) throws OutOfResourcesException { if (session == null) { @@ -533,7 +715,7 @@ public class SurfaceControl { } } - public void setRelativeLayer(IBinder relativeTo, int zorder) { + public void setRelativeLayer(SurfaceControl relativeTo, int zorder) { checkNotReleased(); synchronized(SurfaceControl.class) { sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder); @@ -970,6 +1152,20 @@ public class SurfaceControl { minLayer, maxLayer, allLayers, useIdentityTransform); } + /** + * Captures a layer and its children into the provided {@link Surface}. + * + * @param layerHandleToken The root layer to capture. + * @param consumer The {@link Surface} to capture the layer into. + * @param rotation Apply a custom clockwise rotation to the screenshot, i.e. + * Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its + * native portrait orientation by default, so this is useful for returning + * captures that are independent of device orientation. + */ + public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) { + nativeCaptureLayers(layerHandleToken, consumer, rotation); + } + public static class Transaction implements Closeable { public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( Transaction.class.getClassLoader(), @@ -1035,9 +1231,9 @@ public class SurfaceControl { return this; } - public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) { + public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) { nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, - relativeTo, z); + relativeTo.getHandle(), z); return this; } diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java index ebb2af45..4eab496e 100644 --- a/android/view/SurfaceView.java +++ b/android/view/SurfaceView.java @@ -16,115 +16,1215 @@ package android.view; -import com.android.layoutlib.bridge.MockView; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; +import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER; +import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER; +import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER; import android.content.Context; +import android.content.res.CompatibilityInfo.Translator; +import android.content.res.Configuration; import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.SystemClock; import android.util.AttributeSet; +import android.util.Log; + +import com.android.internal.view.SurfaceCallbackHelper; + +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantLock; /** - * Mock version of the SurfaceView. - * Only non override public methods from the real SurfaceView have been added in there. - * Methods that take an unknown class as parameter or as return object, have been removed for now. + * Provides a dedicated drawing surface embedded inside of a view hierarchy. + * You can control the format of this surface and, if you like, its size; the + * SurfaceView takes care of placing the surface at the correct location on the + * screen + * + * <p>The surface is Z ordered so that it is behind the window holding its + * SurfaceView; the SurfaceView punches a hole in its window to allow its + * surface to be displayed. The view hierarchy will take care of correctly + * compositing with the Surface any siblings of the SurfaceView that would + * normally appear on top of it. This can be used to place overlays such as + * buttons on top of the Surface, though note however that it can have an + * impact on performance since a full alpha-blended composite will be performed + * each time the Surface changes. + * + * <p> The transparent region that makes the surface visible is based on the + * layout positions in the view hierarchy. If the post-layout transform + * properties are used to draw a sibling view on top of the SurfaceView, the + * view may not be properly composited with the surface. * - * TODO: generate automatically. + * <p>Access to the underlying surface is provided via the SurfaceHolder interface, + * which can be retrieved by calling {@link #getHolder}. * + * <p>The Surface will be created for you while the SurfaceView's window is + * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated} + * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the + * Surface is created and destroyed as the window is shown and hidden. + * + * <p>One of the purposes of this class is to provide a surface in which a + * secondary thread can render into the screen. If you are going to use it + * this way, you need to be aware of some threading semantics: + * + * <ul> + * <li> All SurfaceView and + * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called + * from the thread running the SurfaceView's window (typically the main thread + * of the application). They thus need to correctly synchronize with any + * state that is also touched by the drawing thread. + * <li> You must ensure that the drawing thread only touches the underlying + * Surface while it is valid -- between + * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()} + * and + * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}. + * </ul> + * + * <p class="note"><strong>Note:</strong> Starting in platform version + * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is + * updated synchronously with other View rendering. This means that translating + * and scaling a SurfaceView on screen will not cause rendering artifacts. Such + * artifacts may occur on previous versions of the platform when its window is + * positioned asynchronously.</p> */ -public class SurfaceView extends MockView { +public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback { + private static final String TAG = "SurfaceView"; + private static final boolean DEBUG = false; + + final ArrayList<SurfaceHolder.Callback> mCallbacks + = new ArrayList<SurfaceHolder.Callback>(); + + final int[] mLocation = new int[2]; + + final ReentrantLock mSurfaceLock = new ReentrantLock(); + final Surface mSurface = new Surface(); // Current surface in use + boolean mDrawingStopped = true; + // We use this to track if the application has produced a frame + // in to the Surface. Up until that point, we should be careful not to punch + // holes. + boolean mDrawFinished = false; + + final Rect mScreenRect = new Rect(); + SurfaceSession mSurfaceSession; + + SurfaceControl mSurfaceControl; + // In the case of format changes we switch out the surface in-place + // we need to preserve the old one until the new one has drawn. + SurfaceControl mDeferredDestroySurfaceControl; + final Rect mTmpRect = new Rect(); + final Configuration mConfiguration = new Configuration(); + + int mSubLayer = APPLICATION_MEDIA_SUBLAYER; + + boolean mIsCreating = false; + private volatile boolean mRtHandlingPositionUpdates = false; + + private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener + = new ViewTreeObserver.OnScrollChangedListener() { + @Override + public void onScrollChanged() { + updateSurface(); + } + }; + + private final ViewTreeObserver.OnPreDrawListener mDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + // reposition ourselves where the surface is + mHaveFrame = getWidth() > 0 && getHeight() > 0; + updateSurface(); + return true; + } + }; + + boolean mRequestedVisible = false; + boolean mWindowVisibility = false; + boolean mLastWindowVisibility = false; + boolean mViewVisibility = false; + boolean mWindowStopped = false; + + int mRequestedWidth = -1; + int mRequestedHeight = -1; + /* Set SurfaceView's format to 565 by default to maintain backward + * compatibility with applications assuming this format. + */ + int mRequestedFormat = PixelFormat.RGB_565; + + boolean mHaveFrame = false; + boolean mSurfaceCreated = false; + long mLastLockTime = 0; + + boolean mVisible = false; + int mWindowSpaceLeft = -1; + int mWindowSpaceTop = -1; + int mSurfaceWidth = -1; + int mSurfaceHeight = -1; + int mFormat = -1; + final Rect mSurfaceFrame = new Rect(); + int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; + private Translator mTranslator; + + private boolean mGlobalListenersAdded; + private boolean mAttachedToWindow; + + private int mSurfaceFlags = SurfaceControl.HIDDEN; + + private int mPendingReportDraws; public SurfaceView(Context context) { this(context, null); } public SurfaceView(Context context, AttributeSet attrs) { - this(context, attrs , 0); + this(context, attrs, 0); } - public SurfaceView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mRenderNode.requestPositionUpdates(this); + + setWillNotDraw(true); + } + + /** + * Return the SurfaceHolder providing access and control over this + * SurfaceView's underlying surface. + * + * @return SurfaceHolder The holder of the surface. + */ + public SurfaceHolder getHolder() { + return mSurfaceHolder; + } + + private void updateRequestedVisibility() { + mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped; + } + + /** @hide */ + @Override + public void windowStopped(boolean stopped) { + mWindowStopped = stopped; + updateRequestedVisibility(); + updateSurface(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + getViewRootImpl().addWindowStoppedCallback(this); + mWindowStopped = false; + + mViewVisibility = getVisibility() == VISIBLE; + updateRequestedVisibility(); + + mAttachedToWindow = true; + mParent.requestTransparentRegion(SurfaceView.this); + if (!mGlobalListenersAdded) { + ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnScrollChangedListener(mScrollChangedListener); + observer.addOnPreDrawListener(mDrawListener); + mGlobalListenersAdded = true; + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mWindowVisibility = visibility == VISIBLE; + updateRequestedVisibility(); + updateSurface(); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + mViewVisibility = visibility == VISIBLE; + boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped; + if (newRequestedVisible != mRequestedVisible) { + // our base class (View) invalidates the layout only when + // we go from/to the GONE state. However, SurfaceView needs + // to request a re-layout when the visibility changes at all. + // This is needed because the transparent region is computed + // as part of the layout phase, and it changes (obviously) when + // the visibility changes. + requestLayout(); + } + mRequestedVisible = newRequestedVisible; + updateSurface(); + } + + private void performDrawFinished() { + if (mPendingReportDraws > 0) { + mDrawFinished = true; + if (mAttachedToWindow) { + notifyDrawFinished(); + invalidate(); + } + } else { + Log.e(TAG, System.identityHashCode(this) + "finished drawing" + + " but no pending report draw (extra call" + + " to draw completion runnable?)"); + } + } + + void notifyDrawFinished() { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + viewRoot.pendingDrawFinished(); + } + mPendingReportDraws--; + } + + @Override + protected void onDetachedFromWindow() { + ViewRootImpl viewRoot = getViewRootImpl(); + // It's possible to create a SurfaceView using the default constructor and never + // attach it to a view hierarchy, this is a common use case when dealing with + // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage + // the lifecycle. Instead of attaching it to a view, he/she can just pass + // the SurfaceHolder forward, most live wallpapers do it. + if (viewRoot != null) { + viewRoot.removeWindowStoppedCallback(this); + } + + mAttachedToWindow = false; + if (mGlobalListenersAdded) { + ViewTreeObserver observer = getViewTreeObserver(); + observer.removeOnScrollChangedListener(mScrollChangedListener); + observer.removeOnPreDrawListener(mDrawListener); + mGlobalListenersAdded = false; + } + + while (mPendingReportDraws > 0) { + notifyDrawFinished(); + } + + mRequestedVisible = false; + + updateSurface(); + if (mSurfaceControl != null) { + mSurfaceControl.destroy(); + } + mSurfaceControl = null; + + mHaveFrame = false; + + super.onDetachedFromWindow(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = mRequestedWidth >= 0 + ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0) + : getDefaultSize(0, widthMeasureSpec); + int height = mRequestedHeight >= 0 + ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0) + : getDefaultSize(0, heightMeasureSpec); + setMeasuredDimension(width, height); + } + + /** @hide */ + @Override + protected boolean setFrame(int left, int top, int right, int bottom) { + boolean result = super.setFrame(left, top, right, bottom); + updateSurface(); + return result; } + @Override public boolean gatherTransparentRegion(Region region) { - return false; + if (isAboveParent() || !mDrawFinished) { + return super.gatherTransparentRegion(region); + } + + boolean opaque = true; + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { + // this view draws, remove it from the transparent region + opaque = super.gatherTransparentRegion(region); + } else if (region != null) { + int w = getWidth(); + int h = getHeight(); + if (w>0 && h>0) { + getLocationInWindow(mLocation); + // otherwise, punch a hole in the whole hierarchy + int l = mLocation[0]; + int t = mLocation[1]; + region.op(l, t, l+w, t+h, Region.Op.UNION); + } + } + if (PixelFormat.formatHasAlpha(mRequestedFormat)) { + opaque = false; + } + return opaque; } + @Override + public void draw(Canvas canvas) { + if (mDrawFinished && !isAboveParent()) { + // draw() is not called when SKIP_DRAW is set + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { + // punch a whole in the view-hierarchy below us + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + } + super.draw(canvas); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (mDrawFinished && !isAboveParent()) { + // draw() is not called when SKIP_DRAW is set + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { + // punch a whole in the view-hierarchy below us + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + } + super.dispatchDraw(canvas); + } + + /** + * Control whether the surface view's surface is placed on top of another + * regular surface view in the window (but still behind the window itself). + * This is typically used to place overlays on top of an underlying media + * surface view. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}. + */ public void setZOrderMediaOverlay(boolean isMediaOverlay) { + mSubLayer = isMediaOverlay + ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER; } + /** + * Control whether the surface view's surface is placed on top of its + * window. Normally it is placed behind the window, to allow it to + * (for the most part) appear to composite with the views in the + * hierarchy. By setting this, you cause it to be placed above the + * window. This means that none of the contents of the window this + * SurfaceView is in will be visible on top of its surface. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. + */ public void setZOrderOnTop(boolean onTop) { + if (onTop) { + mSubLayer = APPLICATION_PANEL_SUBLAYER; + } else { + mSubLayer = APPLICATION_MEDIA_SUBLAYER; + } } + /** + * Control whether the surface view's content should be treated as secure, + * preventing it from appearing in screenshots or from being viewed on + * non-secure displays. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>See {@link android.view.Display#FLAG_SECURE} for details. + * + * @param isSecure True if the surface view is secure. + */ public void setSecure(boolean isSecure) { + if (isSecure) { + mSurfaceFlags |= SurfaceControl.SECURE; + } else { + mSurfaceFlags &= ~SurfaceControl.SECURE; + } } - public SurfaceHolder getHolder() { - return mSurfaceHolder; + private void updateOpaqueFlag() { + if (!PixelFormat.formatHasAlpha(mRequestedFormat)) { + mSurfaceFlags |= SurfaceControl.OPAQUE; + } else { + mSurfaceFlags &= ~SurfaceControl.OPAQUE; + } } - private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { + private Rect getParentSurfaceInsets() { + final ViewRootImpl root = getViewRootImpl(); + if (root == null) { + return null; + } else { + return root.mWindowAttributes.surfaceInsets; + } + } + + /** @hide */ + protected void updateSurface() { + if (!mHaveFrame) { + return; + } + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { + return; + } + + mTranslator = viewRoot.mTranslator; + if (mTranslator != null) { + mSurface.setCompatibilityTranslator(mTranslator); + } + + int myWidth = mRequestedWidth; + if (myWidth <= 0) myWidth = getWidth(); + int myHeight = mRequestedHeight; + if (myHeight <= 0) myHeight = getHeight(); + + final boolean formatChanged = mFormat != mRequestedFormat; + final boolean visibleChanged = mVisible != mRequestedVisible; + final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged) + && mRequestedVisible; + final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight; + final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility; + boolean redrawNeeded = false; + + if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) { + getLocationInWindow(mLocation); + + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "Changes: creating=" + creating + + " format=" + formatChanged + " size=" + sizeChanged + + " visible=" + visibleChanged + + " left=" + (mWindowSpaceLeft != mLocation[0]) + + " top=" + (mWindowSpaceTop != mLocation[1])); + + try { + final boolean visible = mVisible = mRequestedVisible; + mWindowSpaceLeft = mLocation[0]; + mWindowSpaceTop = mLocation[1]; + mSurfaceWidth = myWidth; + mSurfaceHeight = myHeight; + mFormat = mRequestedFormat; + mLastWindowVisibility = mWindowVisibility; + + mScreenRect.left = mWindowSpaceLeft; + mScreenRect.top = mWindowSpaceTop; + mScreenRect.right = mWindowSpaceLeft + getWidth(); + mScreenRect.bottom = mWindowSpaceTop + getHeight(); + if (mTranslator != null) { + mTranslator.translateRectInAppWindowToScreen(mScreenRect); + } + + final Rect surfaceInsets = getParentSurfaceInsets(); + mScreenRect.offset(surfaceInsets.left, surfaceInsets.top); + + if (creating) { + mSurfaceSession = new SurfaceSession(viewRoot.mSurface); + mDeferredDestroySurfaceControl = mSurfaceControl; + + updateOpaqueFlag(); + final String name = "SurfaceView - " + viewRoot.getTitle().toString(); + + mSurfaceControl = new SurfaceControlWithBackground( + name, + (mSurfaceFlags & SurfaceControl.OPAQUE) != 0, + new SurfaceControl.Builder(mSurfaceSession) + .setSize(mSurfaceWidth, mSurfaceHeight) + .setFormat(mFormat) + .setFlags(mSurfaceFlags)); + } else if (mSurfaceControl == null) { + return; + } + + boolean realSizeChanged = false; + + mSurfaceLock.lock(); + try { + mDrawingStopped = !visible; + + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "Cur surface: " + mSurface); + + SurfaceControl.openTransaction(); + try { + mSurfaceControl.setLayer(mSubLayer); + if (mViewVisibility) { + mSurfaceControl.show(); + } else { + mSurfaceControl.hide(); + } + + // While creating the surface, we will set it's initial + // geometry. Outside of that though, we should generally + // leave it to the RenderThread. + // + // There is one more case when the buffer size changes we aren't yet + // prepared to sync (as even following the transaction applying + // we still need to latch a buffer). + // b/28866173 + if (sizeChanged || creating || !mRtHandlingPositionUpdates) { + mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top); + mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth, + 0.0f, 0.0f, + mScreenRect.height() / (float) mSurfaceHeight); + } + if (sizeChanged) { + mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight); + } + } finally { + SurfaceControl.closeTransaction(); + } + + if (sizeChanged || creating) { + redrawNeeded = true; + } + + mSurfaceFrame.left = 0; + mSurfaceFrame.top = 0; + if (mTranslator == null) { + mSurfaceFrame.right = mSurfaceWidth; + mSurfaceFrame.bottom = mSurfaceHeight; + } else { + float appInvertedScale = mTranslator.applicationInvertedScale; + mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f); + mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f); + } + + final int surfaceWidth = mSurfaceFrame.right; + final int surfaceHeight = mSurfaceFrame.bottom; + realSizeChanged = mLastSurfaceWidth != surfaceWidth + || mLastSurfaceHeight != surfaceHeight; + mLastSurfaceWidth = surfaceWidth; + mLastSurfaceHeight = surfaceHeight; + } finally { + mSurfaceLock.unlock(); + } + + try { + redrawNeeded |= visible && !mDrawFinished; + + SurfaceHolder.Callback callbacks[] = null; + + final boolean surfaceChanged = creating; + if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) { + mSurfaceCreated = false; + if (mSurface.isValid()) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "visibleChanged -- surfaceDestroyed"); + callbacks = getSurfaceCallbacks(); + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } + // Since Android N the same surface may be reused and given to us + // again by the system server at a later point. However + // as we didn't do this in previous releases, clients weren't + // necessarily required to clean up properly in + // surfaceDestroyed. This leads to problems for example when + // clients don't destroy their EGL context, and try + // and create a new one on the same surface following reuse. + // Since there is no valid use of the surface in-between + // surfaceDestroyed and surfaceCreated, we force a disconnect, + // so the next connect will always work if we end up reusing + // the surface. + if (mSurface.isValid()) { + mSurface.forceScopedDisconnect(); + } + } + } + + if (creating) { + mSurface.copyFrom(mSurfaceControl); + } + + if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.O) { + // Some legacy applications use the underlying native {@link Surface} object + // as a key to whether anything has changed. In these cases, updates to the + // existing {@link Surface} will be ignored when the size changes. + // Therefore, we must explicitly recreate the {@link Surface} in these + // cases. + mSurface.createFrom(mSurfaceControl); + } + + if (visible && mSurface.isValid()) { + if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { + mSurfaceCreated = true; + mIsCreating = true; + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "visibleChanged -- surfaceCreated"); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceCreated(mSurfaceHolder); + } + } + if (creating || formatChanged || sizeChanged + || visibleChanged || realSizeChanged) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "surfaceChanged -- format=" + mFormat + + " w=" + myWidth + " h=" + myHeight); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight); + } + } + if (redrawNeeded) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "surfaceRedrawNeeded"); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + + mPendingReportDraws++; + viewRoot.drawPending(); + SurfaceCallbackHelper sch = + new SurfaceCallbackHelper(this::onDrawFinished); + sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks); + } + } + } finally { + mIsCreating = false; + if (mSurfaceControl != null && !mSurfaceCreated) { + mSurface.release(); + // If we are not in the stopped state, then the destruction of the Surface + // represents a visual change we need to display, and we should go ahead + // and destroy the SurfaceControl. However if we are in the stopped state, + // we can just leave the Surface around so it can be a part of animations, + // and we let the life-time be tied to the parent surface. + if (!mWindowStopped) { + mSurfaceControl.destroy(); + mSurfaceControl = null; + } + } + } + } catch (Exception ex) { + Log.e(TAG, "Exception configuring surface", ex); + } + if (DEBUG) Log.v( + TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top + + " w=" + mScreenRect.width() + " h=" + mScreenRect.height() + + ", frame=" + mSurfaceFrame); + } else { + // Calculate the window position in case RT loses the window + // and we need to fallback to a UI-thread driven position update + getLocationInSurface(mLocation); + final boolean positionChanged = mWindowSpaceLeft != mLocation[0] + || mWindowSpaceTop != mLocation[1]; + final boolean layoutSizeChanged = getWidth() != mScreenRect.width() + || getHeight() != mScreenRect.height(); + if (positionChanged || layoutSizeChanged) { // Only the position has changed + mWindowSpaceLeft = mLocation[0]; + mWindowSpaceTop = mLocation[1]; + // For our size changed check, we keep mScreenRect.width() and mScreenRect.height() + // in view local space. + mLocation[0] = getWidth(); + mLocation[1] = getHeight(); + + mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop, + mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]); + + if (mTranslator != null) { + mTranslator.translateRectInAppWindowToScreen(mScreenRect); + } + + if (mSurfaceControl == null) { + return; + } + + if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) { + try { + if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " + + "postion = [%d, %d, %d, %d]", System.identityHashCode(this), + mScreenRect.left, mScreenRect.top, + mScreenRect.right, mScreenRect.bottom)); + setParentSpaceRectangle(mScreenRect, -1); + } catch (Exception ex) { + Log.e(TAG, "Exception configuring surface", ex); + } + } + } + } + } + + private void onDrawFinished() { + if (DEBUG) { + Log.i(TAG, System.identityHashCode(this) + " " + + "finishedDrawing"); + } + + if (mDeferredDestroySurfaceControl != null) { + mDeferredDestroySurfaceControl.destroy(); + mDeferredDestroySurfaceControl = null; + } + + runOnUiThread(() -> { + performDrawFinished(); + }); + } + + private void setParentSpaceRectangle(Rect position, long frameNumber) { + ViewRootImpl viewRoot = getViewRootImpl(); + + SurfaceControl.openTransaction(); + try { + if (frameNumber > 0) { + mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber); + } + mSurfaceControl.setPosition(position.left, position.top); + mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth, + 0.0f, 0.0f, + position.height() / (float) mSurfaceHeight); + } finally { + SurfaceControl.closeTransaction(); + } + } + + private Rect mRTLastReportedPosition = new Rect(); + + /** + * Called by native by a Rendering Worker thread to update the window position + * @hide + */ + public final void updateSurfacePosition_renderWorker(long frameNumber, + int left, int top, int right, int bottom) { + if (mSurfaceControl == null) { + return; + } + + // TODO: This is teensy bit racey in that a brand new SurfaceView moving on + // its 2nd frame if RenderThread is running slowly could potentially see + // this as false, enter the branch, get pre-empted, then this comes along + // and reports a new position, then the UI thread resumes and reports + // its position. This could therefore be de-sync'd in that interval, but + // the synchronization would violate the rule that RT must never block + // on the UI thread which would open up potential deadlocks. The risk of + // a single-frame desync is therefore preferable for now. + mRtHandlingPositionUpdates = true; + if (mRTLastReportedPosition.left == left + && mRTLastReportedPosition.top == top + && mRTLastReportedPosition.right == right + && mRTLastReportedPosition.bottom == bottom) { + return; + } + try { + if (DEBUG) { + Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " + + "postion = [%d, %d, %d, %d]", System.identityHashCode(this), + frameNumber, left, top, right, bottom)); + } + mRTLastReportedPosition.set(left, top, right, bottom); + setParentSpaceRectangle(mRTLastReportedPosition, frameNumber); + // Now overwrite mRTLastReportedPosition with our values + } catch (Exception ex) { + Log.e(TAG, "Exception from repositionChild", ex); + } + } + + /** + * Called by native on RenderThread to notify that the view is no longer in the + * draw tree. UI thread is blocked at this point. + * @hide + */ + public final void surfacePositionLost_uiRtSync(long frameNumber) { + if (DEBUG) { + Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", + System.identityHashCode(this), frameNumber)); + } + mRTLastReportedPosition.setEmpty(); + + if (mSurfaceControl == null) { + return; + } + if (mRtHandlingPositionUpdates) { + mRtHandlingPositionUpdates = false; + // This callback will happen while the UI thread is blocked, so we can + // safely access other member variables at this time. + // So do what the UI thread would have done if RT wasn't handling position + // updates. + if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) { + try { + if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " + + "postion = [%d, %d, %d, %d]", System.identityHashCode(this), + mScreenRect.left, mScreenRect.top, + mScreenRect.right, mScreenRect.bottom)); + setParentSpaceRectangle(mScreenRect, frameNumber); + } catch (Exception ex) { + Log.e(TAG, "Exception configuring surface", ex); + } + } + } + } + + private SurfaceHolder.Callback[] getSurfaceCallbacks() { + SurfaceHolder.Callback callbacks[]; + synchronized (mCallbacks) { + callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; + mCallbacks.toArray(callbacks); + } + return callbacks; + } + + /** + * This method still exists only for compatibility reasons because some applications have relied + * on this method via reflection. See Issue 36345857 for details. + * + * @deprecated No platform code is using this method anymore. + * @hide + */ + @Deprecated + public void setWindowType(int type) { + if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) { + throw new UnsupportedOperationException( + "SurfaceView#setWindowType() has never been a public API."); + } + + if (type == TYPE_APPLICATION_PANEL) { + Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) " + + "just to make the SurfaceView to be placed on top of its window, you must " + + "call setZOrderOnTop(true) instead.", new Throwable()); + setZOrderOnTop(true); + return; + } + Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. " + + "type=" + type, new Throwable()); + } + + private void runOnUiThread(Runnable runnable) { + Handler handler = getHandler(); + if (handler != null && handler.getLooper() != Looper.myLooper()) { + handler.post(runnable); + } else { + runnable.run(); + } + } + + /** + * Check to see if the surface has fixed size dimensions or if the surface's + * dimensions are dimensions are dependent on its current layout. + * + * @return true if the surface has dimensions that are fixed in size + * @hide + */ + public boolean isFixedSize() { + return (mRequestedWidth != -1 || mRequestedHeight != -1); + } + + private boolean isAboveParent() { + return mSubLayer >= 0; + } + + private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() { + private static final String LOG_TAG = "SurfaceHolder"; @Override public boolean isCreating() { - return false; + return mIsCreating; } @Override public void addCallback(Callback callback) { + synchronized (mCallbacks) { + // This is a linear search, but in practice we'll + // have only a couple callbacks, so it doesn't matter. + if (mCallbacks.contains(callback) == false) { + mCallbacks.add(callback); + } + } } @Override public void removeCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } } @Override public void setFixedSize(int width, int height) { + if (mRequestedWidth != width || mRequestedHeight != height) { + mRequestedWidth = width; + mRequestedHeight = height; + requestLayout(); + } } @Override public void setSizeFromLayout() { + if (mRequestedWidth != -1 || mRequestedHeight != -1) { + mRequestedWidth = mRequestedHeight = -1; + requestLayout(); + } } @Override public void setFormat(int format) { + // for backward compatibility reason, OPAQUE always + // means 565 for SurfaceView + if (format == PixelFormat.OPAQUE) + format = PixelFormat.RGB_565; + + mRequestedFormat = format; + if (mSurfaceControl != null) { + updateSurface(); + } } + /** + * @deprecated setType is now ignored. + */ @Override - public void setType(int type) { - } + @Deprecated + public void setType(int type) { } @Override public void setKeepScreenOn(boolean screenOn) { + runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn)); } + /** + * Gets a {@link Canvas} for drawing into the SurfaceView's Surface + * + * After drawing into the provided {@link Canvas}, the caller must + * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. + * + * The caller must redraw the entire surface. + * @return A canvas for drawing into the surface. + */ @Override public Canvas lockCanvas() { - return null; + return internalLockCanvas(null, false); + } + + /** + * Gets a {@link Canvas} for drawing into the SurfaceView's Surface + * + * After drawing into the provided {@link Canvas}, the caller must + * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. + * + * @param inOutDirty A rectangle that represents the dirty region that the caller wants + * to redraw. This function may choose to expand the dirty rectangle if for example + * the surface has been resized or if the previous contents of the surface were + * not available. The caller must redraw the entire dirty region as represented + * by the contents of the inOutDirty rectangle upon return from this function. + * The caller may also pass <code>null</code> instead, in the case where the + * entire surface should be redrawn. + * @return A canvas for drawing into the surface. + */ + @Override + public Canvas lockCanvas(Rect inOutDirty) { + return internalLockCanvas(inOutDirty, false); } @Override - public Canvas lockCanvas(Rect dirty) { + public Canvas lockHardwareCanvas() { + return internalLockCanvas(null, true); + } + + private Canvas internalLockCanvas(Rect dirty, boolean hardware) { + mSurfaceLock.lock(); + + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped=" + + mDrawingStopped + ", surfaceControl=" + mSurfaceControl); + + Canvas c = null; + if (!mDrawingStopped && mSurfaceControl != null) { + try { + if (hardware) { + c = mSurface.lockHardwareCanvas(); + } else { + c = mSurface.lockCanvas(dirty); + } + } catch (Exception e) { + Log.e(LOG_TAG, "Exception locking surface", e); + } + } + + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c); + if (c != null) { + mLastLockTime = SystemClock.uptimeMillis(); + return c; + } + + // If the Surface is not ready to be drawn, then return null, + // but throttle calls to this function so it isn't called more + // than every 100ms. + long now = SystemClock.uptimeMillis(); + long nextTime = mLastLockTime + 100; + if (nextTime > now) { + try { + Thread.sleep(nextTime-now); + } catch (InterruptedException e) { + } + now = SystemClock.uptimeMillis(); + } + mLastLockTime = now; + mSurfaceLock.unlock(); + return null; } + /** + * Posts the new contents of the {@link Canvas} to the surface and + * releases the {@link Canvas}. + * + * @param canvas The canvas previously obtained from {@link #lockCanvas}. + */ @Override public void unlockCanvasAndPost(Canvas canvas) { + mSurface.unlockCanvasAndPost(canvas); + mSurfaceLock.unlock(); } @Override public Surface getSurface() { - return null; + return mSurface; } @Override public Rect getSurfaceFrame() { - return null; + return mSurfaceFrame; } }; -} + class SurfaceControlWithBackground extends SurfaceControl { + private SurfaceControl mBackgroundControl; + private boolean mOpaque = true; + public boolean mVisible = false; + + public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b) + throws Exception { + super(b.setName(name).build()); + + mBackgroundControl = b.setName("Background for -" + name) + .setFormat(OPAQUE) + .setColorLayer(true) + .build(); + mOpaque = opaque; + } + + @Override + public void setAlpha(float alpha) { + super.setAlpha(alpha); + mBackgroundControl.setAlpha(alpha); + } + + @Override + public void setLayer(int zorder) { + super.setLayer(zorder); + // -3 is below all other child layers as SurfaceView never goes below -2 + mBackgroundControl.setLayer(-3); + } + + @Override + public void setPosition(float x, float y) { + super.setPosition(x, y); + mBackgroundControl.setPosition(x, y); + } + + @Override + public void setSize(int w, int h) { + super.setSize(w, h); + mBackgroundControl.setSize(w, h); + } + + @Override + public void setWindowCrop(Rect crop) { + super.setWindowCrop(crop); + mBackgroundControl.setWindowCrop(crop); + } + + @Override + public void setFinalCrop(Rect crop) { + super.setFinalCrop(crop); + mBackgroundControl.setFinalCrop(crop); + } + + @Override + public void setLayerStack(int layerStack) { + super.setLayerStack(layerStack); + mBackgroundControl.setLayerStack(layerStack); + } + + @Override + public void setOpaque(boolean isOpaque) { + super.setOpaque(isOpaque); + mOpaque = isOpaque; + updateBackgroundVisibility(); + } + + @Override + public void setSecure(boolean isSecure) { + super.setSecure(isSecure); + } + + @Override + public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { + super.setMatrix(dsdx, dtdx, dsdy, dtdy); + mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy); + } + + @Override + public void hide() { + super.hide(); + mVisible = false; + updateBackgroundVisibility(); + } + + @Override + public void show() { + super.show(); + mVisible = true; + updateBackgroundVisibility(); + } + + @Override + public void destroy() { + super.destroy(); + mBackgroundControl.destroy(); + } + + @Override + public void release() { + super.release(); + mBackgroundControl.release(); + } + + @Override + public void setTransparentRegionHint(Region region) { + super.setTransparentRegionHint(region); + mBackgroundControl.setTransparentRegionHint(region); + } + + @Override + public void deferTransactionUntil(IBinder handle, long frame) { + super.deferTransactionUntil(handle, frame); + mBackgroundControl.deferTransactionUntil(handle, frame); + } + + @Override + public void deferTransactionUntil(Surface barrier, long frame) { + super.deferTransactionUntil(barrier, frame); + mBackgroundControl.deferTransactionUntil(barrier, frame); + } + + void updateBackgroundVisibility() { + if (mOpaque && mVisible) { + mBackgroundControl.show(); + } else { + mBackgroundControl.hide(); + } + } + } +} diff --git a/android/view/TouchDelegate.java b/android/view/TouchDelegate.java index cf36f436..dc50fa1d 100644 --- a/android/view/TouchDelegate.java +++ b/android/view/TouchDelegate.java @@ -44,7 +44,7 @@ public class TouchDelegate { /** * mBounds inflated to include some slop. This rect is to track whether the motion events - * should be considered to be be within the delegate view. + * should be considered to be within the delegate view. */ private Rect mSlopBounds; @@ -64,14 +64,12 @@ public class TouchDelegate { public static final int BELOW = 2; /** - * The touchable region of the View extends to the left of its - * actual extent. + * The touchable region of the View extends to the left of its actual extent. */ public static final int TO_LEFT = 4; /** - * The touchable region of the View extends to the right of its - * actual extent. + * The touchable region of the View extends to the right of its actual extent. */ public static final int TO_RIGHT = 8; @@ -108,28 +106,24 @@ public class TouchDelegate { boolean handled = false; switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - Rect bounds = mBounds; - - if (bounds.contains(x, y)) { - mDelegateTargeted = true; - sendToDelegate = true; - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_MOVE: - sendToDelegate = mDelegateTargeted; - if (sendToDelegate) { - Rect slopBounds = mSlopBounds; - if (!slopBounds.contains(x, y)) { - hit = false; + case MotionEvent.ACTION_DOWN: + mDelegateTargeted = mBounds.contains(x, y); + sendToDelegate = mDelegateTargeted; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_MOVE: + sendToDelegate = mDelegateTargeted; + if (sendToDelegate) { + Rect slopBounds = mSlopBounds; + if (!slopBounds.contains(x, y)) { + hit = false; + } } - } - break; - case MotionEvent.ACTION_CANCEL: - sendToDelegate = mDelegateTargeted; - mDelegateTargeted = false; - break; + break; + case MotionEvent.ACTION_CANCEL: + sendToDelegate = mDelegateTargeted; + mDelegateTargeted = false; + break; } if (sendToDelegate) { final View delegateView = mDelegateView; diff --git a/android/view/ViewGroup_Delegate.java b/android/view/ViewGroup_Delegate.java index 4b760a7d..6daae200 100644 --- a/android/view/ViewGroup_Delegate.java +++ b/android/view/ViewGroup_Delegate.java @@ -67,12 +67,12 @@ public class ViewGroup_Delegate { Outline outline) { float elevation = getElevation(child, parent); if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) { - RectShadowPainter.paintShadow(outline, elevation, canvas); + RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha()); return; } BufferedImage shadow = null; if (outline.mPath != null) { - shadow = getPathShadow(outline, canvas, elevation); + shadow = getPathShadow(outline, canvas, elevation, child.getAlpha()); } if (shadow == null) { return; @@ -91,7 +91,8 @@ public class ViewGroup_Delegate { return child.getZ() - parent.getZ(); } - private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) { + private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation, + float alpha) { Rect clipBounds = canvas.getClipBounds(); if (clipBounds.isEmpty()) { return null; @@ -101,7 +102,7 @@ public class ViewGroup_Delegate { Graphics2D graphics = image.createGraphics(); graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape()); graphics.dispose(); - return ShadowPainter.createDropShadow(image, (int) elevation); + return ShadowPainter.createDropShadow(image, (int) elevation, alpha); } // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java index 99438d87..37829f0b 100644 --- a/android/view/ViewRootImpl.java +++ b/android/view/ViewRootImpl.java @@ -2284,18 +2284,36 @@ public final class ViewRootImpl implements ViewParent, } } - if (mFirst && sAlwaysAssignFocus) { - // handle first focus request - if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()=" - + mView.hasFocus()); - if (mView != null) { - if (!mView.hasFocus()) { - mView.restoreDefaultFocus(); - if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view=" - + mView.findFocus()); - } else { - if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view=" - + mView.findFocus()); + if (mFirst) { + if (sAlwaysAssignFocus) { + // handle first focus request + if (DEBUG_INPUT_RESIZE) { + Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus()); + } + if (mView != null) { + if (!mView.hasFocus()) { + mView.restoreDefaultFocus(); + if (DEBUG_INPUT_RESIZE) { + Log.v(mTag, "First: requested focused view=" + mView.findFocus()); + } + } else { + if (DEBUG_INPUT_RESIZE) { + Log.v(mTag, "First: existing focused view=" + mView.findFocus()); + } + } + } + } else { + // Some views (like ScrollView) won't hand focus to descendants that aren't within + // their viewport. Before layout, there's a good change these views are size 0 + // which means no children can get focus. After layout, this view now has size, but + // is not guaranteed to hand-off focus to a focusable child (specifically, the edge- + // case where the child has a size prior to layout and thus won't trigger + // focusableViewAvailable). + View focused = mView.findFocus(); + if (focused instanceof ViewGroup + && ((ViewGroup) focused).getDescendantFocusability() + == ViewGroup.FOCUS_AFTER_DESCENDANTS) { + focused.restoreDefaultFocus(); } } } diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java index 11cb046a..0b9bc576 100644 --- a/android/view/accessibility/AccessibilityManager.java +++ b/android/view/accessibility/AccessibilityManager.java @@ -16,46 +16,152 @@ package android.view.accessibility; +import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME; + +import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemService; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.os.Binder; import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; import android.view.IWindow; import android.view.View; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IntPair; + +import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * System level service that serves as an event dispatch for {@link AccessibilityEvent}s. - * Such events are generated when something notable happens in the user interface, + * System level service that serves as an event dispatch for {@link AccessibilityEvent}s, + * and provides facilities for querying the accessibility state of the system. + * Accessibility events are generated when something notable happens in the user interface, * for example an {@link android.app.Activity} starts, the focus or selection of a * {@link android.view.View} changes etc. Parties interested in handling accessibility * events implement and register an accessibility service which extends - * {@code android.accessibilityservice.AccessibilityService}. + * {@link android.accessibilityservice.AccessibilityService}. * * @see AccessibilityEvent - * @see android.content.Context#getSystemService + * @see AccessibilityNodeInfo + * @see android.accessibilityservice.AccessibilityService + * @see Context#getSystemService + * @see Context#ACCESSIBILITY_SERVICE */ -@SuppressWarnings("UnusedDeclaration") +@SystemService(Context.ACCESSIBILITY_SERVICE) public final class AccessibilityManager { + private static final boolean DEBUG = false; + + private static final String LOG_TAG = "AccessibilityManager"; + + /** @hide */ + public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001; + + /** @hide */ + public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; + + /** @hide */ + public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004; + + /** @hide */ + public static final int DALTONIZER_DISABLED = -1; + + /** @hide */ + public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0; + + /** @hide */ + public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12; + + /** @hide */ + public static final int AUTOCLICK_DELAY_DEFAULT = 600; + + /** + * Activity action: Launch UI to manage which accessibility service or feature is assigned + * to the navigation bar Accessibility button. + * <p> + * Input: Nothing. + * </p> + * <p> + * Output: Nothing. + * </p> + * + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON = + "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON"; + + static final Object sInstanceSync = new Object(); + + private static AccessibilityManager sInstance; + + private final Object mLock = new Object(); + + private IAccessibilityManager mService; + + final int mUserId; + + final Handler mHandler; + + final Handler.Callback mCallback; + + boolean mIsEnabled; - private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0); + int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; + boolean mIsTouchExplorationEnabled; + + boolean mIsHighTextContrastEnabled; + + private final ArrayMap<AccessibilityStateChangeListener, Handler> + mAccessibilityStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<TouchExplorationStateChangeListener, Handler> + mTouchExplorationStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<HighTextContrastChangeListener, Handler> + mHighTextContrastStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<AccessibilityServicesStateChangeListener, Handler> + mServicesStateChangeListeners = new ArrayMap<>(); + + /** + * Map from a view's accessibility id to the list of request preparers set for that view + */ + private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists; /** - * Listener for the accessibility state. + * Listener for the system accessibility state. To listen for changes to the + * accessibility state on the device, implement this interface and register + * it with the system by calling {@link #addAccessibilityStateChangeListener}. */ public interface AccessibilityStateChangeListener { /** - * Called back on change in the accessibility state. + * Called when the accessibility enabled state changes. * * @param enabled Whether accessibility is enabled. */ - public void onAccessibilityStateChanged(boolean enabled); + void onAccessibilityStateChanged(boolean enabled); } /** @@ -71,7 +177,24 @@ public final class AccessibilityManager { * * @param enabled Whether touch exploration is enabled. */ - public void onTouchExplorationStateChanged(boolean enabled); + void onTouchExplorationStateChanged(boolean enabled); + } + + /** + * Listener for changes to the state of accessibility services. Changes include services being + * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service. + * {@see #addAccessibilityServicesStateChangeListener}. + * + * @hide + */ + public interface AccessibilityServicesStateChangeListener { + + /** + * Called when the state of accessibility services changes. + * + * @param manager The manager that is calling back + */ + void onAccessibilityServicesStateChanged(AccessibilityManager manager); } /** @@ -79,6 +202,8 @@ public final class AccessibilityManager { * the high text contrast state on the device, implement this interface and * register it with the system by calling * {@link #addHighTextContrastStateChangeListener}. + * + * @hide */ public interface HighTextContrastChangeListener { @@ -87,26 +212,72 @@ public final class AccessibilityManager { * * @param enabled Whether high text contrast is enabled. */ - public void onHighTextContrastStateChanged(boolean enabled); + void onHighTextContrastStateChanged(boolean enabled); } private final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { - public void setState(int state) { - } + @Override + public void setState(int state) { + // We do not want to change this immediately as the application may + // have already checked that accessibility is on and fired an event, + // that is now propagating up the view tree, Hence, if accessibility + // is now off an exception will be thrown. We want to have the exception + // enforcement to guard against apps that fire unnecessary accessibility + // events when accessibility is off. + mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget(); + } - public void notifyServicesStateChanged() { + @Override + public void notifyServicesStateChanged() { + final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mServicesStateChangeListeners.isEmpty()) { + return; } + listeners = new ArrayMap<>(mServicesStateChangeListeners); + } - public void setRelevantEventTypes(int eventTypes) { - } - }; + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final AccessibilityServicesStateChangeListener listener = + mServicesStateChangeListeners.keyAt(i); + mServicesStateChangeListeners.valueAt(i).post(() -> listener + .onAccessibilityServicesStateChanged(AccessibilityManager.this)); + } + } + + @Override + public void setRelevantEventTypes(int eventTypes) { + mRelevantEventTypes = eventTypes; + } + }; /** * Get an AccessibilityManager instance (create one if necessary). * + * @param context Context in which this manager operates. + * + * @hide */ public static AccessibilityManager getInstance(Context context) { + synchronized (sInstanceSync) { + if (sInstance == null) { + final int userId; + if (Binder.getCallingUid() == Process.SYSTEM_UID + || context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS) + == PackageManager.PERMISSION_GRANTED + || context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + userId = UserHandle.USER_CURRENT; + } else { + userId = UserHandle.myUserId(); + } + sInstance = new AccessibilityManager(context, null, userId); + } + } return sInstance; } @@ -114,21 +285,68 @@ public final class AccessibilityManager { * Create an instance. * * @param context A {@link Context}. + * @param service An interface to the backing service. + * @param userId User id under which to run. + * + * @hide */ public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { + // Constructor can't be chained because we can't create an instance of an inner class + // before calling another constructor. + mCallback = new MyCallback(); + mHandler = new Handler(context.getMainLooper(), mCallback); + mUserId = userId; + synchronized (mLock) { + tryConnectToServiceLocked(service); + } + } + + /** + * Create an instance. + * + * @param handler The handler to use + * @param service An interface to the backing service. + * @param userId User id under which to run. + * + * @hide + */ + public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) { + mCallback = new MyCallback(); + mHandler = handler; + mUserId = userId; + synchronized (mLock) { + tryConnectToServiceLocked(service); + } } + /** + * @hide + */ public IAccessibilityManagerClient getClient() { return mClient; } /** - * Returns if the {@link AccessibilityManager} is enabled. + * @hide + */ + @VisibleForTesting + public Handler.Callback getCallback() { + return mCallback; + } + + /** + * Returns if the accessibility in the system is enabled. * - * @return True if this {@link AccessibilityManager} is enabled, false otherwise. + * @return True if accessibility is enabled, false otherwise. */ public boolean isEnabled() { - return false; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsEnabled; + } } /** @@ -137,7 +355,13 @@ public final class AccessibilityManager { * @return True if touch exploration is enabled, false otherwise. */ public boolean isTouchExplorationEnabled() { - return true; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsTouchExplorationEnabled; + } } /** @@ -147,35 +371,169 @@ public final class AccessibilityManager { * doing its own rendering and does not rely on the platform rendering pipeline. * </p> * + * @return True if high text contrast is enabled, false otherwise. + * + * @hide */ public boolean isHighTextContrastEnabled() { - return false; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsHighTextContrastEnabled; + } } /** * Sends an {@link AccessibilityEvent}. + * + * @param event The event to send. + * + * @throws IllegalStateException if accessibility is not enabled. + * + * <strong>Note:</strong> The preferred mechanism for sending custom accessibility + * events is through calling + * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} + * instead of this method to allow predecessors to augment/filter events sent by + * their descendants. */ public void sendAccessibilityEvent(AccessibilityEvent event) { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + if (!mIsEnabled) { + Looper myLooper = Looper.myLooper(); + if (myLooper == Looper.getMainLooper()) { + throw new IllegalStateException( + "Accessibility off. Did you forget to check that?"); + } else { + // If we're not running on the thread with the main looper, it's possible for + // the state of accessibility to change between checking isEnabled and + // calling this method. So just log the error rather than throwing the + // exception. + Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled"); + return; + } + } + if ((event.getEventType() & mRelevantEventTypes) == 0) { + if (DEBUG) { + Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event + + " that is not among " + + AccessibilityEvent.eventTypeToString(mRelevantEventTypes)); + } + return; + } + userId = mUserId; + } + try { + event.setEventTime(SystemClock.uptimeMillis()); + // it is possible that this manager is in the same process as the service but + // client using it is called through Binder from another process. Example: MMS + // app adds a SMS notification and the NotificationManagerService calls this method + long identityToken = Binder.clearCallingIdentity(); + service.sendAccessibilityEvent(event, userId); + Binder.restoreCallingIdentity(identityToken); + if (DEBUG) { + Log.i(LOG_TAG, event + " sent"); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error during sending " + event + " ", re); + } finally { + event.recycle(); + } } /** - * Requests interruption of the accessibility feedback from all accessibility services. + * Requests feedback interruption from all accessibility services. */ public void interrupt() { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + if (!mIsEnabled) { + Looper myLooper = Looper.myLooper(); + if (myLooper == Looper.getMainLooper()) { + throw new IllegalStateException( + "Accessibility off. Did you forget to check that?"); + } else { + // If we're not running on the thread with the main looper, it's possible for + // the state of accessibility to change between checking isEnabled and + // calling this method. So just log the error rather than throwing the + // exception. + Log.e(LOG_TAG, "Interrupt called with accessibility disabled"); + return; + } + } + userId = mUserId; + } + try { + service.interrupt(userId); + if (DEBUG) { + Log.i(LOG_TAG, "Requested interrupt from all services"); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); + } } /** * Returns the {@link ServiceInfo}s of the installed accessibility services. * * @return An unmodifiable list with {@link ServiceInfo}s. + * + * @deprecated Use {@link #getInstalledAccessibilityServiceList()} */ @Deprecated public List<ServiceInfo> getAccessibilityServiceList() { - return Collections.emptyList(); + List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList(); + List<ServiceInfo> services = new ArrayList<>(); + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + AccessibilityServiceInfo info = infos.get(i); + services.add(info.getResolveInfo().serviceInfo); + } + return Collections.unmodifiableList(services); } + /** + * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services. + * + * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. + */ public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { - return Collections.emptyList(); + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return Collections.emptyList(); + } + userId = mUserId; + } + + List<AccessibilityServiceInfo> services = null; + try { + services = service.getInstalledAccessibilityServiceList(userId); + if (DEBUG) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); + } + if (services != null) { + return Collections.unmodifiableList(services); + } else { + return Collections.emptyList(); + } } /** @@ -190,21 +548,48 @@ public final class AccessibilityManager { * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN * @see AccessibilityServiceInfo#FEEDBACK_VISUAL + * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE */ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( int feedbackTypeFlags) { - return Collections.emptyList(); + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return Collections.emptyList(); + } + userId = mUserId; + } + + List<AccessibilityServiceInfo> services = null; + try { + services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); + if (DEBUG) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); + } + if (services != null) { + return Collections.unmodifiableList(services); + } else { + return Collections.emptyList(); + } } /** * Registers an {@link AccessibilityStateChangeListener} for changes in - * the global accessibility state of the system. + * the global accessibility state of the system. Equivalent to calling + * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)} + * with a null handler. * * @param listener The listener. - * @return True if successfully registered. + * @return Always returns {@code true}. */ public boolean addAccessibilityStateChangeListener( - AccessibilityStateChangeListener listener) { + @NonNull AccessibilityStateChangeListener listener) { + addAccessibilityStateChangeListener(listener, null); return true; } @@ -218,22 +603,40 @@ public final class AccessibilityManager { * for a callback on the process's main handler. */ public void addAccessibilityStateChangeListener( - @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {} + @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mAccessibilityStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } + /** + * Unregisters an {@link AccessibilityStateChangeListener}. + * + * @param listener The listener. + * @return True if the listener was previously registered. + */ public boolean removeAccessibilityStateChangeListener( - AccessibilityStateChangeListener listener) { - return true; + @NonNull AccessibilityStateChangeListener listener) { + synchronized (mLock) { + int index = mAccessibilityStateChangeListeners.indexOfKey(listener); + mAccessibilityStateChangeListeners.remove(listener); + return (index >= 0); + } } /** * Registers a {@link TouchExplorationStateChangeListener} for changes in - * the global touch exploration state of the system. + * the global touch exploration state of the system. Equivalent to calling + * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)} + * with a null handler. * * @param listener The listener. - * @return True if successfully registered. + * @return Always returns {@code true}. */ public boolean addTouchExplorationStateChangeListener( @NonNull TouchExplorationStateChangeListener listener) { + addTouchExplorationStateChangeListener(listener, null); return true; } @@ -247,17 +650,103 @@ public final class AccessibilityManager { * for a callback on the process's main handler. */ public void addTouchExplorationStateChangeListener( - @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {} + @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mTouchExplorationStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } /** * Unregisters a {@link TouchExplorationStateChangeListener}. * * @param listener The listener. - * @return True if successfully unregistered. + * @return True if listener was previously registered. */ public boolean removeTouchExplorationStateChangeListener( @NonNull TouchExplorationStateChangeListener listener) { - return true; + synchronized (mLock) { + int index = mTouchExplorationStateChangeListeners.indexOfKey(listener); + mTouchExplorationStateChangeListeners.remove(listener); + return (index >= 0); + } + } + + /** + * Registers a {@link AccessibilityServicesStateChangeListener}. + * + * @param listener The listener. + * @param handler The handler on which the listener should be called back, or {@code null} + * for a callback on the process's main handler. + * @hide + */ + public void addAccessibilityServicesStateChangeListener( + @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mServicesStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } + + /** + * Unregisters a {@link AccessibilityServicesStateChangeListener}. + * + * @param listener The listener. + * + * @hide + */ + public void removeAccessibilityServicesStateChangeListener( + @NonNull AccessibilityServicesStateChangeListener listener) { + // Final CopyOnWriteArrayList - no lock needed. + mServicesStateChangeListeners.remove(listener); + } + + /** + * Registers a {@link AccessibilityRequestPreparer}. + */ + public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { + if (mRequestPreparerLists == null) { + mRequestPreparerLists = new SparseArray<>(1); + } + int id = preparer.getView().getAccessibilityViewId(); + List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id); + if (requestPreparerList == null) { + requestPreparerList = new ArrayList<>(1); + mRequestPreparerLists.put(id, requestPreparerList); + } + requestPreparerList.add(preparer); + } + + /** + * Unregisters a {@link AccessibilityRequestPreparer}. + */ + public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { + if (mRequestPreparerLists == null) { + return; + } + int viewId = preparer.getView().getAccessibilityViewId(); + List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId); + if (requestPreparerList != null) { + requestPreparerList.remove(preparer); + if (requestPreparerList.isEmpty()) { + mRequestPreparerLists.remove(viewId); + } + } + } + + /** + * Get the preparers that are registered for an accessibility ID + * + * @param id The ID of interest + * @return The list of preparers, or {@code null} if there are none. + * + * @hide + */ + public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) { + if (mRequestPreparerLists == null) { + return null; + } + return mRequestPreparerLists.get(id); } /** @@ -269,7 +758,12 @@ public final class AccessibilityManager { * @hide */ public void addHighTextContrastStateChangeListener( - @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {} + @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mHighTextContrastStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } /** * Unregisters a {@link HighTextContrastChangeListener}. @@ -279,7 +773,51 @@ public final class AccessibilityManager { * @hide */ public void removeHighTextContrastStateChangeListener( - @NonNull HighTextContrastChangeListener listener) {} + @NonNull HighTextContrastChangeListener listener) { + synchronized (mLock) { + mHighTextContrastStateChangeListeners.remove(listener); + } + } + + /** + * Check if the accessibility volume stream is active. + * + * @return True if accessibility volume is active (i.e. some service has requested it). False + * otherwise. + * @hide + */ + public boolean isAccessibilityVolumeStreamActive() { + List<AccessibilityServiceInfo> serviceInfos = + getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + for (int i = 0; i < serviceInfos.size(); i++) { + if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) { + return true; + } + } + return false; + } + + /** + * Report a fingerprint gesture to accessibility. Only available for the system process. + * + * @param keyCode The key code of the gesture + * @return {@code true} if accessibility consumes the event. {@code false} if not. + * @hide + */ + public boolean sendFingerprintGesture(int keyCode) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return false; + } + } + try { + return service.sendFingerprintGesture(keyCode); + } catch (RemoteException e) { + return false; + } + } /** * Sets the current state and notifies listeners, if necessary. @@ -287,14 +825,314 @@ public final class AccessibilityManager { * @param stateFlags The state flags. */ private void setStateLocked(int stateFlags) { + final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; + final boolean touchExplorationEnabled = + (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; + final boolean highTextContrastEnabled = + (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0; + + final boolean wasEnabled = mIsEnabled; + final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; + final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled; + + // Ensure listeners get current state from isZzzEnabled() calls. + mIsEnabled = enabled; + mIsTouchExplorationEnabled = touchExplorationEnabled; + mIsHighTextContrastEnabled = highTextContrastEnabled; + + if (wasEnabled != enabled) { + notifyAccessibilityStateChanged(); + } + + if (wasTouchExplorationEnabled != touchExplorationEnabled) { + notifyTouchExplorationStateChanged(); + } + + if (wasHighTextContrastEnabled != highTextContrastEnabled) { + notifyHighTextContrastStateChanged(); + } } + /** + * Find an installed service with the specified {@link ComponentName}. + * + * @param componentName The name to match to the service. + * + * @return The info corresponding to the installed service, or {@code null} if no such service + * is installed. + * @hide + */ + public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName( + ComponentName componentName) { + final List<AccessibilityServiceInfo> installedServiceInfos = + getInstalledAccessibilityServiceList(); + if ((installedServiceInfos == null) || (componentName == null)) { + return null; + } + for (int i = 0; i < installedServiceInfos.size(); i++) { + if (componentName.equals(installedServiceInfos.get(i).getComponentName())) { + return installedServiceInfos.get(i); + } + } + return null; + } + + /** + * Adds an accessibility interaction connection interface for a given window. + * @param windowToken The window token to which a connection is added. + * @param connection The connection. + * + * @hide + */ public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return View.NO_ID; + } + userId = mUserId; + } + try { + return service.addAccessibilityInteractionConnection(windowToken, connection, userId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); + } return View.NO_ID; } + /** + * Removed an accessibility interaction connection interface for a given window. + * @param windowToken The window token to which a connection is removed. + * + * @hide + */ public void removeAccessibilityInteractionConnection(IWindow windowToken) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.removeAccessibilityInteractionConnection(windowToken); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); + } + } + + /** + * Perform the accessibility shortcut if the caller has permission. + * + * @hide + */ + public void performAccessibilityShortcut() { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.performAccessibilityShortcut(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re); + } + } + + /** + * Notifies that the accessibility button in the system's navigation area has been clicked + * + * @hide + */ + public void notifyAccessibilityButtonClicked() { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.notifyAccessibilityButtonClicked(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while dispatching accessibility button click", re); + } + } + + /** + * Notifies that the visibility of the accessibility button in the system's navigation area + * has changed. + * + * @param shown {@code true} if the accessibility button is visible within the system + * navigation area, {@code false} otherwise + * @hide + */ + public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.notifyAccessibilityButtonVisibilityChanged(shown); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re); + } + } + + /** + * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture + * window. Intended for use by the System UI only. + * + * @param connection The connection to handle the actions. Set to {@code null} to avoid + * affecting the actions. + * + * @hide + */ + public void setPictureInPictureActionReplacingConnection( + @Nullable IAccessibilityInteractionConnection connection) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.setPictureInPictureActionReplacingConnection(connection); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error setting picture in picture action replacement", re); + } } + private IAccessibilityManager getServiceLocked() { + if (mService == null) { + tryConnectToServiceLocked(null); + } + return mService; + } + + private void tryConnectToServiceLocked(IAccessibilityManager service) { + if (service == null) { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + if (iBinder == null) { + return; + } + service = IAccessibilityManager.Stub.asInterface(iBinder); + } + + try { + final long userStateAndRelevantEvents = service.addClient(mClient, mUserId); + setStateLocked(IntPair.first(userStateAndRelevantEvents)); + mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents); + mService = service; + } catch (RemoteException re) { + Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); + } + } + + /** + * Notifies the registered {@link AccessibilityStateChangeListener}s. + */ + private void notifyAccessibilityStateChanged() { + final boolean isEnabled; + final ArrayMap<AccessibilityStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mAccessibilityStateChangeListeners.isEmpty()) { + return; + } + isEnabled = mIsEnabled; + listeners = new ArrayMap<>(mAccessibilityStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final AccessibilityStateChangeListener listener = + mAccessibilityStateChangeListeners.keyAt(i); + mAccessibilityStateChangeListeners.valueAt(i) + .post(() -> listener.onAccessibilityStateChanged(isEnabled)); + } + } + + /** + * Notifies the registered {@link TouchExplorationStateChangeListener}s. + */ + private void notifyTouchExplorationStateChanged() { + final boolean isTouchExplorationEnabled; + final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mTouchExplorationStateChangeListeners.isEmpty()) { + return; + } + isTouchExplorationEnabled = mIsTouchExplorationEnabled; + listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final TouchExplorationStateChangeListener listener = + mTouchExplorationStateChangeListeners.keyAt(i); + mTouchExplorationStateChangeListeners.valueAt(i) + .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled)); + } + } + + /** + * Notifies the registered {@link HighTextContrastChangeListener}s. + */ + private void notifyHighTextContrastStateChanged() { + final boolean isHighTextContrastEnabled; + final ArrayMap<HighTextContrastChangeListener, Handler> listeners; + synchronized (mLock) { + if (mHighTextContrastStateChangeListeners.isEmpty()) { + return; + } + isHighTextContrastEnabled = mIsHighTextContrastEnabled; + listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final HighTextContrastChangeListener listener = + mHighTextContrastStateChangeListeners.keyAt(i); + mHighTextContrastStateChangeListeners.valueAt(i) + .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled)); + } + } + + /** + * Determines if the accessibility button within the system navigation area is supported. + * + * @return {@code true} if the accessibility button is supported on this device, + * {@code false} otherwise + */ + public static boolean isAccessibilityButtonSupported() { + final Resources res = Resources.getSystem(); + return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); + } + + private final class MyCallback implements Handler.Callback { + public static final int MSG_SET_STATE = 1; + + @Override + public boolean handleMessage(Message message) { + switch (message.what) { + case MSG_SET_STATE: { + // See comment at mClient + final int state = message.arg1; + synchronized (mLock) { + setStateLocked(state); + } + } break; + } + return true; + } + } } diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java index e564fa34..e79d201b 100644 --- a/android/view/autofill/AutofillManager.java +++ b/android/view/autofill/AutofillManager.java @@ -24,6 +24,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; @@ -224,7 +225,7 @@ public final class AutofillManager { /** * State where the autofill context was finished by the server because the autofill - * service could not autofill the page. + * service could not autofill the activity. * * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored, * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}). @@ -242,6 +243,16 @@ public final class AutofillManager { public static final int STATE_SHOWING_SAVE_UI = 3; /** + * State where the autofill is disabled because the service cannot autofill the activity at all. + * + * <p>In this state, every call is ignored, even {@link #requestAutofill(View)} + * (and {@link #requestAutofill(View, int, Rect)}). + * + * @hide + */ + public static final int STATE_DISABLED_BY_SERVICE = 4; + + /** * Makes an authentication id from a request id and a dataset id. * * @param requestId The request id. @@ -398,6 +409,11 @@ public final class AutofillManager { * Runs the specified action on the UI thread. */ void runOnUiThread(Runnable action); + + /** + * Gets the complete component name of this client. + */ + ComponentName getComponentName(); } /** @@ -506,7 +522,7 @@ public final class AutofillManager { * @return whether autofill is enabled for the current user. */ public boolean isEnabled() { - if (!hasAutofillFeature()) { + if (!hasAutofillFeature() || isDisabledByService()) { return false; } synchronized (mLock) { @@ -580,19 +596,31 @@ public final class AutofillManager { notifyViewEntered(view, 0); } + private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) { + if (isDisabledByService()) { + if (sVerbose) { + Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view + + ") on state " + getStateAsStringLocked()); + } + return true; + } + if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) { + if (sVerbose) { + Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view + + ") on state " + getStateAsStringLocked()); + } + return true; + } + return false; + } + private void notifyViewEntered(@NonNull View view, int flags) { if (!hasAutofillFeature()) { return; } AutofillCallback callback = null; synchronized (mLock) { - if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { - if (sVerbose) { - Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view - + "): ignored on state " + getStateAsStringLocked()); - } - return; - } + if (shouldIgnoreViewEnteredLocked(view, flags)) return; ensureServiceClientAddedIfNeededLocked(); @@ -717,14 +745,8 @@ public final class AutofillManager { } AutofillCallback callback = null; synchronized (mLock) { - if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { - if (sVerbose) { - Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view - + ", virtualId=" + virtualId - + "): ignored on state " + getStateAsStringLocked()); - } - return; - } + if (shouldIgnoreViewEnteredLocked(view, flags)) return; + ensureServiceClientAddedIfNeededLocked(); if (!mEnabled) { @@ -1059,13 +1081,13 @@ public final class AutofillManager { return; } try { + final AutofillClient client = getClientLocked(); mSessionId = mService.startSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, mContext.getOpPackageName()); + mCallback != null, flags, client.getComponentName()); if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } - final AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } @@ -1120,14 +1142,14 @@ public final class AutofillManager { try { if (restartIfNecessary) { + final AutofillClient client = getClientLocked(); final int newId = mService.updateOrRestartSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action); + mCallback != null, flags, client.getComponentName(), mSessionId, action); if (newId != mSessionId) { if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); mSessionId = newId; mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; - final AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } @@ -1437,7 +1459,9 @@ public final class AutofillManager { * Marks the state of the session as finished. * * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null} - * FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed). + * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or + * {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill + * requests for the activity). */ private void setSessionFinished(int newState) { synchronized (mLock) { @@ -1482,10 +1506,10 @@ public final class AutofillManager { } } - private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) { + private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { if (sVerbose) { Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id - + ", finished=" + sessionFinished); + + ", sessionFinishedState=" + sessionFinishedState); } final View anchor = findView(id); if (anchor == null) { @@ -1508,9 +1532,9 @@ public final class AutofillManager { } } - if (sessionFinished) { + if (sessionFinishedState != 0) { // Callback call was "hijacked" to also update the session state. - setSessionFinished(STATE_FINISHED); + setSessionFinished(sessionFinishedState); } } @@ -1613,6 +1637,8 @@ public final class AutofillManager { return "STATE_FINISHED"; case STATE_SHOWING_SAVE_UI: return "STATE_SHOWING_SAVE_UI"; + case STATE_DISABLED_BY_SERVICE: + return "STATE_DISABLED_BY_SERVICE"; default: return "INVALID:" + mState; } @@ -1622,8 +1648,8 @@ public final class AutofillManager { return mState == STATE_ACTIVE; } - private boolean isFinishedLocked() { - return mState == STATE_FINISHED; + private boolean isDisabledByService() { + return mState == STATE_DISABLED_BY_SERVICE; } private void post(Runnable runnable) { @@ -1957,10 +1983,10 @@ public final class AutofillManager { } @Override - public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) { + public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished)); + afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState)); } } diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java index 8c3b8a2e..26d2141e 100644 --- a/android/view/textclassifier/TextClassification.java +++ b/android/view/textclassifier/TextClassification.java @@ -33,6 +33,52 @@ import java.util.List; /** * Information for generating a widget to handle classified text. + * + * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may + * be used to build a widget that can be used to act on classified text. + * + * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app: + * + * <pre>{@code + * // Called preferably outside the UiThread. + * TextClassification classification = textClassifier.classifyText(allText, 10, 25, null); + * + * // Called on the UiThread. + * Button button = new Button(context); + * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null); + * button.setText(classification.getLabel()); + * button.setOnClickListener(classification.getOnClickListener()); + * }</pre> + * + * <p>e.g. starting an action mode with menu items that can handle the classified text: + * + * <pre>{@code + * // Called preferably outside the UiThread. + * final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null); + * + * // Called on the UiThread. + * view.startActionMode(new ActionMode.Callback() { + * + * public boolean onCreateActionMode(ActionMode mode, Menu menu) { + * for (int i = 0; i < classification.getActionCount(); i++) { + * if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) { + * menu.add(Menu.NONE, i, 20, classification.getLabel(i)) + * .setIcon(classification.getIcon(i)) + * .setIntent(classification.getIntent(i)); + * } + * } + * return true; + * } + * + * public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + * context.startActivity(item.getIntent()); + * return true; + * } + * + * ... + * }); + * }</pre> + * */ public final class TextClassification { diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java index c3601d9d..46dbd0e3 100644 --- a/android/view/textclassifier/TextClassifier.java +++ b/android/view/textclassifier/TextClassifier.java @@ -29,8 +29,8 @@ import java.lang.annotation.RetentionPolicy; /** * Interface for providing text classification related features. * - * <p>Unless otherwise stated, methods of this interface are blocking operations and you should - * avoid calling them on the UI thread. + * <p>Unless otherwise stated, methods of this interface are blocking operations. + * Avoid calling them on the UI thread. */ public interface TextClassifier { @@ -75,8 +75,8 @@ public interface TextClassifier { }; /** - * Returns suggested text selection indices, recognized types and their associated confidence - * scores. The selections are ordered from highest to lowest scoring. + * Returns suggested text selection start and end indices, recognized entity types, and their + * associated confidence scores. The entity types are ordered from highest to lowest scoring. * * @param text text providing context for the selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java index 8e1f2183..f368c74a 100644 --- a/android/view/textservice/TextServicesManager.java +++ b/android/view/textservice/TextServicesManager.java @@ -1,58 +1,213 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2011 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 + * 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 + * 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. + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ package android.view.textservice; +import android.annotation.SystemService; +import android.content.Context; import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.Log; import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; +import com.android.internal.textservice.ITextServicesManager; + import java.util.Locale; /** - * A stub class of TextServicesManager for Layout-Lib. + * System API to the overall text services, which arbitrates interaction between applications + * and text services. + * + * The user can change the current text services in Settings. And also applications can specify + * the target text services. + * + * <h3>Architecture Overview</h3> + * + * <p>There are three primary parties involved in the text services + * framework (TSF) architecture:</p> + * + * <ul> + * <li> The <strong>text services manager</strong> as expressed by this class + * is the central point of the system that manages interaction between all + * other parts. It is expressed as the client-side API here which exists + * in each application context and communicates with a global system service + * that manages the interaction across all processes. + * <li> A <strong>text service</strong> implements a particular + * interaction model allowing the client application to retrieve information of text. + * The system binds to the current text service that is in use, causing it to be created and run. + * <li> Multiple <strong>client applications</strong> arbitrate with the text service + * manager for connections to text services. + * </ul> + * + * <h3>Text services sessions</h3> + * <ul> + * <li>The <strong>spell checker session</strong> is one of the text services. + * {@link android.view.textservice.SpellCheckerSession}</li> + * </ul> + * */ +@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) public final class TextServicesManager { - private static final TextServicesManager sInstance = new TextServicesManager(); - private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0]; + private static final String TAG = TextServicesManager.class.getSimpleName(); + private static final boolean DBG = false; + + private static TextServicesManager sInstance; + + private final ITextServicesManager mService; + + private TextServicesManager() throws ServiceNotFoundException { + mService = ITextServicesManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE)); + } /** * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist. * @hide */ public static TextServicesManager getInstance() { - return sInstance; + synchronized (TextServicesManager.class) { + if (sInstance == null) { + try { + sInstance = new TextServicesManager(); + } catch (ServiceNotFoundException e) { + throw new IllegalStateException(e); + } + } + return sInstance; + } + } + + /** + * Returns the language component of a given locale string. + */ + private static String parseLanguageFromLocaleString(String locale) { + final int idx = locale.indexOf('_'); + if (idx < 0) { + return locale; + } else { + return locale.substring(0, idx); + } } + /** + * Get a spell checker session for the specified spell checker + * @param locale the locale for the spell checker. If {@code locale} is null and + * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be + * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true, + * the locale specified in Settings will be returned only when it is same as {@code locale}. + * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is + * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be + * selected. + * @param listener a spell checker session lister for getting results from a spell checker. + * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled + * languages in settings will be returned. + * @return the spell checker session of the spell checker + */ public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale, SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { - return null; + if (listener == null) { + throw new NullPointerException(); + } + if (!referToSpellCheckerLanguageSettings && locale == null) { + throw new IllegalArgumentException("Locale should not be null if you don't refer" + + " settings."); + } + + if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) { + return null; + } + + final SpellCheckerInfo sci; + try { + sci = mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + return null; + } + if (sci == null) { + return null; + } + SpellCheckerSubtype subtypeInUse = null; + if (referToSpellCheckerLanguageSettings) { + subtypeInUse = getCurrentSpellCheckerSubtype(true); + if (subtypeInUse == null) { + return null; + } + if (locale != null) { + final String subtypeLocale = subtypeInUse.getLocale(); + final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale); + if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) { + return null; + } + } + } else { + final String localeStr = locale.toString(); + for (int i = 0; i < sci.getSubtypeCount(); ++i) { + final SpellCheckerSubtype subtype = sci.getSubtypeAt(i); + final String tempSubtypeLocale = subtype.getLocale(); + final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale); + if (tempSubtypeLocale.equals(localeStr)) { + subtypeInUse = subtype; + break; + } else if (tempSubtypeLanguage.length() >= 2 && + locale.getLanguage().equals(tempSubtypeLanguage)) { + subtypeInUse = subtype; + } + } + } + if (subtypeInUse == null) { + return null; + } + final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener); + try { + mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(), + session.getTextServicesSessionListener(), + session.getSpellCheckerSessionListener(), bundle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return session; } /** * @hide */ public SpellCheckerInfo[] getEnabledSpellCheckers() { - return EMPTY_SPELL_CHECKER_INFO; + try { + final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers(); + if (DBG) { + Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null")); + } + return retval; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * @hide */ public SpellCheckerInfo getCurrentSpellChecker() { - return null; + try { + // Passing null as a locale for ICS + return mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -60,13 +215,22 @@ public final class TextServicesManager { */ public SpellCheckerSubtype getCurrentSpellCheckerSubtype( boolean allowImplicitlySelectedSubtype) { - return null; + try { + // Passing null as a locale until we support multiple enabled spell checker subtypes. + return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * @hide */ public boolean isSpellCheckerEnabled() { - return false; + try { + return mService.isSpellCheckerEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } diff --git a/android/webkit/MimeTypeMap.java b/android/webkit/MimeTypeMap.java index 39874e82..38616952 100644 --- a/android/webkit/MimeTypeMap.java +++ b/android/webkit/MimeTypeMap.java @@ -37,7 +37,7 @@ public class MimeTypeMap { } /** - * Returns the file extension or an empty string iff there is no + * Returns the file extension or an empty string if there is no * extension. This method is a convenience method for obtaining the * extension of a url and has undefined results for other Strings. * @param url @@ -76,7 +76,7 @@ public class MimeTypeMap { /** * Return {@code true} if the given MIME type has an entry in the map. * @param mimeType A MIME type (i.e. text/plain) - * @return {@code true} iff there is a mimeType entry in the map. + * @return {@code true} if there is a mimeType entry in the map. */ public boolean hasMimeType(String mimeType) { return MimeUtils.hasMimeType(mimeType); @@ -85,7 +85,7 @@ public class MimeTypeMap { /** * Return the MIME type for the given extension. * @param extension A file extension without the leading '.' - * @return The MIME type for the given extension or {@code null} iff there is none. + * @return The MIME type for the given extension or {@code null} if there is none. */ @Nullable public String getMimeTypeFromExtension(String extension) { @@ -100,7 +100,7 @@ public class MimeTypeMap { /** * Return {@code true} if the given extension has a registered MIME type. * @param extension A file extension without the leading '.' - * @return {@code true} iff there is an extension entry in the map. + * @return {@code true} if there is an extension entry in the map. */ public boolean hasExtension(String extension) { return MimeUtils.hasExtension(extension); @@ -111,7 +111,7 @@ public class MimeTypeMap { * MIME types map to multiple extensions. This call will return the most * common extension for the given MIME type. * @param mimeType A MIME type (i.e. text/plain) - * @return The extension for the given MIME type or {@code null} iff there is none. + * @return The extension for the given MIME type or {@code null} if there is none. */ @Nullable public String getExtensionFromMimeType(String mimeType) { diff --git a/android/webkit/URLUtil.java b/android/webkit/URLUtil.java index c2f121a1..84c000a3 100644 --- a/android/webkit/URLUtil.java +++ b/android/webkit/URLUtil.java @@ -137,7 +137,7 @@ public final class URLUtil { } /** - * @return {@code true} iff the url is correctly URL encoded + * @return {@code true} if the url is correctly URL encoded */ static boolean verifyURLEncoding(String url) { int count = url.length(); @@ -171,14 +171,14 @@ public final class URLUtil { } /** - * @return {@code true} iff the url is an asset file. + * @return {@code true} if the url is an asset file. */ public static boolean isAssetUrl(String url) { return (null != url) && url.startsWith(ASSET_BASE); } /** - * @return {@code true} iff the url is a resource file. + * @return {@code true} if the url is a resource file. * @hide */ public static boolean isResourceUrl(String url) { @@ -186,7 +186,7 @@ public final class URLUtil { } /** - * @return {@code true} iff the url is a proxy url to allow cookieless network + * @return {@code true} if the url is a proxy url to allow cookieless network * requests from a file url. * @deprecated Cookieless proxy is no longer supported. */ @@ -196,7 +196,7 @@ public final class URLUtil { } /** - * @return {@code true} iff the url is a local file. + * @return {@code true} if the url is a local file. */ public static boolean isFileUrl(String url) { return (null != url) && (url.startsWith(FILE_BASE) && @@ -205,28 +205,28 @@ public final class URLUtil { } /** - * @return {@code true} iff the url is an about: url. + * @return {@code true} if the url is an about: url. */ public static boolean isAboutUrl(String url) { return (null != url) && url.startsWith("about:"); } /** - * @return {@code true} iff the url is a data: url. + * @return {@code true} if the url is a data: url. */ public static boolean isDataUrl(String url) { return (null != url) && url.startsWith("data:"); } /** - * @return {@code true} iff the url is a javascript: url. + * @return {@code true} if the url is a javascript: url. */ public static boolean isJavaScriptUrl(String url) { return (null != url) && url.startsWith("javascript:"); } /** - * @return {@code true} iff the url is an http: url. + * @return {@code true} if the url is an http: url. */ public static boolean isHttpUrl(String url) { return (null != url) && @@ -235,7 +235,7 @@ public final class URLUtil { } /** - * @return {@code true} iff the url is an https: url. + * @return {@code true} if the url is an https: url. */ public static boolean isHttpsUrl(String url) { return (null != url) && @@ -244,7 +244,7 @@ public final class URLUtil { } /** - * @return {@code true} iff the url is a network url. + * @return {@code true} if the url is a network url. */ public static boolean isNetworkUrl(String url) { if (url == null || url.length() == 0) { @@ -254,14 +254,14 @@ public final class URLUtil { } /** - * @return {@code true} iff the url is a content: url. + * @return {@code true} if the url is a content: url. */ public static boolean isContentUrl(String url) { return (null != url) && url.startsWith(CONTENT_BASE); } /** - * @return {@code true} iff the url is valid. + * @return {@code true} if the url is valid. */ public static boolean isValidUrl(String url) { if (url == null || url.length() == 0) { diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java index 202f2046..259bf60a 100644 --- a/android/webkit/WebView.java +++ b/android/webkit/WebView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2006 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,223 +16,3001 @@ package android.webkit; -import com.android.layoutlib.bridge.MockView; - +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.Widget; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Picture; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.net.http.SslCertificate; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.os.StrictMode; +import android.print.PrintDocumentAdapter; +import android.security.KeyChain; import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseArray; +import android.view.DragEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; +import android.view.ViewStructure; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeProvider; +import android.view.autofill.AutofillValue; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.textclassifier.TextClassifier; +import android.widget.AbsoluteLayout; + +import java.io.BufferedWriter; +import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Map; /** - * Mock version of the WebView. - * Only non override public methods from the real WebView have been added in there. - * Methods that take an unknown class as parameter or as return object, have been removed for now. - * - * TODO: generate automatically. + * <p>A View that displays web pages. This class is the basis upon which you + * can roll your own web browser or simply display some online content within your Activity. + * It uses the WebKit rendering engine to display + * web pages and includes methods to navigate forward and backward + * through a history, zoom in and out, perform text searches and more. + * + * <p>Note that, in order for your Activity to access the Internet and load web pages + * in a WebView, you must add the {@code INTERNET} permissions to your + * Android Manifest file: + * + * <pre> + * {@code <uses-permission android:name="android.permission.INTERNET" />} + * </pre> + * + * <p>This must be a child of the <a + * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a> + * element. + * + * <p>For more information, read + * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>. + * + * <h3>Basic usage</h3> + * + * <p>By default, a WebView provides no browser-like widgets, does not + * enable JavaScript and web page errors are ignored. If your goal is only + * to display some HTML as a part of your UI, this is probably fine; + * the user won't need to interact with the web page beyond reading + * it, and the web page won't need to interact with the user. If you + * actually want a full-blown web browser, then you probably want to + * invoke the Browser application with a URL Intent rather than show it + * with a WebView. For example: + * <pre> + * Uri uri = Uri.parse("https://www.example.com"); + * Intent intent = new Intent(Intent.ACTION_VIEW, uri); + * startActivity(intent); + * </pre> + * <p>See {@link android.content.Intent} for more information. + * + * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout, + * or set the entire Activity window as a WebView during {@link + * android.app.Activity#onCreate(Bundle) onCreate()}: + * + * <pre class="prettyprint"> + * WebView webview = new WebView(this); + * setContentView(webview); + * </pre> + * + * <p>Then load the desired web page: + * + * <pre> + * // Simplest usage: note that an exception will NOT be thrown + * // if there is an error loading this page (see below). + * webview.loadUrl("https://example.com/"); + * + * // OR, you can also load from an HTML string: + * String summary = "<html><body>You scored <b>192</b> points.</body></html>"; + * webview.loadData(summary, "text/html", null); + * // ... although note that there are restrictions on what this HTML can do. + * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link + * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info. + * </pre> + * + * <p>A WebView has several customization points where you can add your + * own behavior. These are: + * + * <ul> + * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass. + * This class is called when something that might impact a + * browser UI happens, for instance, progress updates and + * JavaScript alerts are sent here (see <a + * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>). + * </li> + * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass. + * It will be called when things happen that impact the + * rendering of the content, eg, errors or form submissions. You + * can also intercept URL loading here (via {@link + * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String) + * shouldOverrideUrlLoading()}).</li> + * <li>Modifying the {@link android.webkit.WebSettings}, such as + * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean) + * setJavaScriptEnabled()}. </li> + * <li>Injecting Java objects into the WebView using the + * {@link android.webkit.WebView#addJavascriptInterface} method. This + * method allows you to inject Java objects into a page's JavaScript + * context, so that they can be accessed by JavaScript in the page.</li> + * </ul> + * + * <p>Here's a more complicated example, showing error handling, + * settings, and progress notification: + * + * <pre class="prettyprint"> + * // Let's display the progress in the activity title bar, like the + * // browser app does. + * getWindow().requestFeature(Window.FEATURE_PROGRESS); + * + * webview.getSettings().setJavaScriptEnabled(true); + * + * final Activity activity = this; + * webview.setWebChromeClient(new WebChromeClient() { + * public void onProgressChanged(WebView view, int progress) { + * // Activities and WebViews measure progress with different scales. + * // The progress meter will automatically disappear when we reach 100% + * activity.setProgress(progress * 1000); + * } + * }); + * webview.setWebViewClient(new WebViewClient() { + * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show(); + * } + * }); + * + * webview.loadUrl("https://developer.android.com/"); + * </pre> + * + * <h3>Zoom</h3> + * + * <p>To enable the built-in zoom, set + * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} + * (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}). + * + * <p>NOTE: Using zoom if either the height or width is set to + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior + * and should be avoided. + * + * <h3>Cookie and window management</h3> + * + * <p>For obvious security reasons, your application has its own + * cache, cookie store etc.—it does not share the Browser + * application's data. + * + * <p>By default, requests by the HTML to open new windows are + * ignored. This is {@code true} whether they be opened by JavaScript or by + * the target attribute on a link. You can customize your + * {@link WebChromeClient} to provide your own behavior for opening multiple windows, + * and render them in whatever manner you want. + * + * <p>The standard behavior for an Activity is to be destroyed and + * recreated when the device orientation or any other configuration changes. This will cause + * the WebView to reload the current page. If you don't want that, you + * can set your Activity to handle the {@code orientation} and {@code keyboardHidden} + * changes, and then just leave the WebView alone. It'll automatically + * re-orient itself as appropriate. Read <a + * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for + * more information about how to handle configuration changes during runtime. + * + * + * <h3>Building web pages to support different screen densities</h3> + * + * <p>The screen density of a device is based on the screen resolution. A screen with low density + * has fewer available pixels per inch, where a screen with high density + * has more — sometimes significantly more — pixels per inch. The density of a + * screen is important because, other things being equal, a UI element (such as a button) whose + * height and width are defined in terms of screen pixels will appear larger on the lower density + * screen and smaller on the higher density screen. + * For simplicity, Android collapses all actual screen densities into three generalized densities: + * high, medium, and low. + * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default + * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen + * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels + * are bigger). + * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS, + * and meta tag features to help you (as a web developer) target screens with different screen + * densities. + * <p>Here's a summary of the features you can use to handle different screen densities: + * <ul> + * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the + * default scaling factor used for the current device. For example, if the value of {@code + * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device + * and default scaling is not applied to the web page; if the value is "1.5", then the device is + * considered a high density device (hdpi) and the page content is scaled 1.5x; if the + * value is "0.75", then the device is considered a low density device (ldpi) and the content is + * scaled 0.75x.</li> + * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen + * densities for which this style sheet is to be used. The corresponding value should be either + * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium + * density, or high density screens, respectively. For example: + * <pre> + * <link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /></pre> + * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5, + * which is the high density pixel ratio. + * </li> + * </ul> + * + * <h3>HTML5 Video support</h3> + * + * <p>In order to support inline HTML5 video in your application you need to have hardware + * acceleration turned on. + * + * <h3>Full screen support</h3> + * + * <p>In order to support full screen — for video or other HTML content — you need to set a + * {@link android.webkit.WebChromeClient} and implement both + * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)} + * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is + * missing then the web contents will not be allowed to enter full screen. Optionally you can implement + * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video + * is loading. + * + * <h3>HTML5 Geolocation API support</h3> + * + * <p>For applications targeting Android N and later releases + * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on + * secure origins such as https. For such applications requests to geolocation api on non-secure + * origins are automatically denied without invoking the corresponding + * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)} + * method. + * + * <h3>Layout size</h3> + * <p> + * It is recommended to set the WebView layout height to a fixed value or to + * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}. + * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} + * for the height none of the WebView's parents should use a + * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in + * incorrect sizing of the views. + * + * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} + * enables the following behaviors: + * <ul> + * <li>The HTML body layout height is set to a fixed value. This means that elements with a height + * relative to the HTML body may not be sized correctly. </li> + * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the + * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li> + * </ul> + * + * <p> + * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not + * supported. If such a width is used the WebView will attempt to use the width of the parent + * instead. + * + * <h3>Metrics</h3> + * + * <p> + * WebView may upload anonymous diagnostic data to Google when the user has consented. This data + * helps Google improve WebView. Data is collected on a per-app basis for each app which has + * instantiated a WebView. An individual app can opt out of this feature by putting the following + * tag in its manifest: + * <pre> + * <meta-data android:name="android.webkit.WebView.MetricsOptOut" + * android:value="true" /> + * </pre> + * <p> + * Data will only be uploaded for a given app if the user has consented AND the app has not opted + * out. + * + * <h3>Safe Browsing</h3> + * + * <p> + * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the + * user to allow them to navigate back safely or proceed to the malicious page. + * <p> + * The recommended way for apps to enable the feature is putting the following tag in the manifest: + * <p> + * <pre> + * <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" + * android:value="true" /> + * </pre> * */ -public class WebView extends MockView { +// Implementation notes. +// The WebView is a thin API class that delegates its public API to a backend WebViewProvider +// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons. +// Methods are delegated to the provider implementation: all public API methods introduced in this +// file are fully delegated, whereas public and protected methods from the View base classes are +// only delegated where a specific need exists for them to do so. +@Widget +public class WebView extends AbsoluteLayout + implements ViewTreeObserver.OnGlobalFocusChangeListener, + ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler { + + private static final String LOGTAG = "WebView"; + + // Throwing an exception for incorrect thread usage if the + // build target is JB MR2 or newer. Defaults to false, and is + // set in the WebView constructor. + private static volatile boolean sEnforceThreadChecking = false; + + /** + * Transportation object for returning WebView across thread boundaries. + */ + public class WebViewTransport { + private WebView mWebview; + /** + * Sets the WebView to the transportation object. + * + * @param webview the WebView to transport + */ + public synchronized void setWebView(WebView webview) { + mWebview = webview; + } + + /** + * Gets the WebView object. + * + * @return the transported WebView object + */ + public synchronized WebView getWebView() { + return mWebview; + } + } + + /** + * URI scheme for telephone number. + */ + public static final String SCHEME_TEL = "tel:"; /** - * Construct a new WebView with a Context object. - * @param context A Context object used to access application assets. + * URI scheme for email address. + */ + public static final String SCHEME_MAILTO = "mailto:"; + /** + * URI scheme for map address. + */ + public static final String SCHEME_GEO = "geo:0,0?q="; + + /** + * Interface to listen for find results. + */ + public interface FindListener { + /** + * Notifies the listener about progress made by a find operation. + * + * @param activeMatchOrdinal the zero-based ordinal of the currently selected match + * @param numberOfMatches how many matches have been found + * @param isDoneCounting whether the find operation has actually completed. The listener + * may be notified multiple times while the + * operation is underway, and the numberOfMatches + * value should not be considered final unless + * isDoneCounting is {@code true}. + */ + public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, + boolean isDoneCounting); + } + + /** + * Callback interface supplied to {@link #postVisualStateCallback} for receiving + * notifications about the visual state. + */ + public static abstract class VisualStateCallback { + /** + * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}. + * + * @param requestId The identifier passed to {@link #postVisualStateCallback} when this + * callback was posted. + */ + public abstract void onComplete(long requestId); + } + + /** + * Interface to listen for new pictures as they change. + * + * @deprecated This interface is now obsolete. + */ + @Deprecated + public interface PictureListener { + /** + * Used to provide notification that the WebView's picture has changed. + * See {@link WebView#capturePicture} for details of the picture. + * + * @param view the WebView that owns the picture + * @param picture the new picture. Applications targeting + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above + * will always receive a {@code null} Picture. + * @deprecated Deprecated due to internal changes. + */ + @Deprecated + void onNewPicture(WebView view, @Nullable Picture picture); + } + + public static class HitTestResult { + /** + * Default HitTestResult, where the target is unknown. + */ + public static final int UNKNOWN_TYPE = 0; + /** + * @deprecated This type is no longer used. + */ + @Deprecated + public static final int ANCHOR_TYPE = 1; + /** + * HitTestResult for hitting a phone number. + */ + public static final int PHONE_TYPE = 2; + /** + * HitTestResult for hitting a map address. + */ + public static final int GEO_TYPE = 3; + /** + * HitTestResult for hitting an email address. + */ + public static final int EMAIL_TYPE = 4; + /** + * HitTestResult for hitting an HTML::img tag. + */ + public static final int IMAGE_TYPE = 5; + /** + * @deprecated This type is no longer used. + */ + @Deprecated + public static final int IMAGE_ANCHOR_TYPE = 6; + /** + * HitTestResult for hitting a HTML::a tag with src=http. + */ + public static final int SRC_ANCHOR_TYPE = 7; + /** + * HitTestResult for hitting a HTML::a tag with src=http + HTML::img. + */ + public static final int SRC_IMAGE_ANCHOR_TYPE = 8; + /** + * HitTestResult for hitting an edit text area. + */ + public static final int EDIT_TEXT_TYPE = 9; + + private int mType; + private String mExtra; + + /** + * @hide Only for use by WebViewProvider implementations + */ + @SystemApi + public HitTestResult() { + mType = UNKNOWN_TYPE; + } + + /** + * @hide Only for use by WebViewProvider implementations + */ + @SystemApi + public void setType(int type) { + mType = type; + } + + /** + * @hide Only for use by WebViewProvider implementations + */ + @SystemApi + public void setExtra(String extra) { + mExtra = extra; + } + + /** + * Gets the type of the hit test result. See the XXX_TYPE constants + * defined in this class. + * + * @return the type of the hit test result + */ + public int getType() { + return mType; + } + + /** + * Gets additional type-dependant information about the result. See + * {@link WebView#getHitTestResult()} for details. May either be {@code null} + * or contain extra information about this result. + * + * @return additional type-dependant information about the result + */ + @Nullable + public String getExtra() { + return mExtra; + } + } + + /** + * Constructs a new WebView with a Context object. + * + * @param context a Context object used to access application assets */ public WebView(Context context) { this(context, null); } /** - * Construct a new WebView with layout parameters. - * @param context A Context object used to access application assets. - * @param attrs An AttributeSet passed to our parent. + * Constructs a new WebView with layout parameters. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent */ public WebView(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.webViewStyle); } /** - * Construct a new WebView with layout parameters and a default style. - * @param context A Context object used to access application assets. - * @param attrs An AttributeSet passed to our parent. - * @param defStyle The default style resource ID. + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. */ - public WebView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public WebView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - - // START FAKE PUBLIC METHODS - + + /** + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes a resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + this(context, attrs, defStyleAttr, defStyleRes, null, false); + } + + /** + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param privateBrowsing whether this WebView will be initialized in + * private mode + * + * @deprecated Private browsing is no longer supported directly via + * WebView and will be removed in a future release. Prefer using + * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager} + * and {@link WebStorage} for fine-grained control of privacy data. + */ + @Deprecated + public WebView(Context context, AttributeSet attrs, int defStyleAttr, + boolean privateBrowsing) { + this(context, attrs, defStyleAttr, 0, null, privateBrowsing); + } + + /** + * Constructs a new WebView with layout parameters, a default style and a set + * of custom JavaScript interfaces to be added to this WebView at initialization + * time. This guarantees that these interfaces will be available when the JS + * context is initialized. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param javaScriptInterfaces a Map of interface names, as keys, and + * object implementing those interfaces, as + * values + * @param privateBrowsing whether this WebView will be initialized in + * private mode + * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to + * be added synchronously, before a subsequent loadUrl call takes effect. + */ + protected WebView(Context context, AttributeSet attrs, int defStyleAttr, + Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { + this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing); + } + + /** + * @hide + */ + @SuppressWarnings("deprecation") // for super() call into deprecated base class constructor. + protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, + Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { + super(context, attrs, defStyleAttr, defStyleRes); + + // WebView is important by default, unless app developer overrode attribute. + if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { + setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); + } + + if (context == null) { + throw new IllegalArgumentException("Invalid context argument"); + } + sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >= + Build.VERSION_CODES.JELLY_BEAN_MR2; + checkThread(); + + ensureProviderCreated(); + mProvider.init(javaScriptInterfaces, privateBrowsing); + // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed. + CookieSyncManager.setGetInstanceIsAllowed(); + } + + /** + * Specifies whether the horizontal scrollbar has overlay style. + * + * @deprecated This method has no effect. + * @param overlay {@code true} if horizontal scrollbar should have overlay style + */ + @Deprecated public void setHorizontalScrollbarOverlay(boolean overlay) { } + /** + * Specifies whether the vertical scrollbar has overlay style. + * + * @deprecated This method has no effect. + * @param overlay {@code true} if vertical scrollbar should have overlay style + */ + @Deprecated public void setVerticalScrollbarOverlay(boolean overlay) { } + /** + * Gets whether horizontal scrollbar has overlay style. + * + * @deprecated This method is now obsolete. + * @return {@code true} + */ + @Deprecated public boolean overlayHorizontalScrollbar() { - return false; + // The old implementation defaulted to true, so return true for consistency + return true; } + /** + * Gets whether vertical scrollbar has overlay style. + * + * @deprecated This method is now obsolete. + * @return {@code false} + */ + @Deprecated public boolean overlayVerticalScrollbar() { + // The old implementation defaulted to false, so return false for consistency return false; } + /** + * Gets the visible height (in pixels) of the embedded title bar (if any). + * + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public int getVisibleTitleHeight() { + checkThread(); + return mProvider.getVisibleTitleHeight(); + } + + /** + * Gets the SSL certificate for the main top-level page or {@code null} if there is + * no certificate (the site is not secure). + * + * @return the SSL certificate for the main top-level page + */ + @Nullable + public SslCertificate getCertificate() { + checkThread(); + return mProvider.getCertificate(); + } + + /** + * Sets the SSL certificate for the main top-level page. + * + * @deprecated Calling this function has no useful effect, and will be + * ignored in future releases. + */ + @Deprecated + public void setCertificate(SslCertificate certificate) { + checkThread(); + mProvider.setCertificate(certificate); + } + + //------------------------------------------------------------------------- + // Methods called by activity + //------------------------------------------------------------------------- + + /** + * Sets a username and password pair for the specified host. This data is + * used by the WebView to autocomplete username and password fields in web + * forms. Note that this is unrelated to the credentials used for HTTP + * authentication. + * + * @param host the host that required the credentials + * @param username the username for the given host + * @param password the password for the given host + * @see WebViewDatabase#clearUsernamePassword + * @see WebViewDatabase#hasUsernamePassword + * @deprecated Saving passwords in WebView will not be supported in future versions. + */ + @Deprecated public void savePassword(String host, String username, String password) { + checkThread(); + mProvider.savePassword(host, username, password); } + /** + * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase} + * instance. + * + * @param host the host to which the credentials apply + * @param realm the realm to which the credentials apply + * @param username the username + * @param password the password + * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead + */ + @Deprecated public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) { + checkThread(); + mProvider.setHttpAuthUsernamePassword(host, realm, username, password); } + /** + * Retrieves HTTP authentication credentials for a given host and realm from the {@link + * WebViewDatabase} instance. + * @param host the host to which the credentials apply + * @param realm the realm to which the credentials apply + * @return the credentials as a String array, if found. The first element + * is the username and the second element is the password. {@code null} if + * no credentials are found. + * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead + */ + @Deprecated + @Nullable public String[] getHttpAuthUsernamePassword(String host, String realm) { - return null; + checkThread(); + return mProvider.getHttpAuthUsernamePassword(host, realm); } + /** + * Destroys the internal state of this WebView. This method should be called + * after this WebView has been removed from the view system. No other + * methods may be called on this WebView after destroy. + */ public void destroy() { + checkThread(); + mProvider.destroy(); } + /** + * Enables platform notifications of data state and proxy changes. + * Notifications are enabled by default. + * + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated public static void enablePlatformNotifications() { + // noop } + /** + * Disables platform notifications of data state and proxy changes. + * Notifications are enabled by default. + * + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated public static void disablePlatformNotifications() { + // noop } + /** + * Used only by internal tests to free up memory. + * + * @hide + */ + public static void freeMemoryForTests() { + getFactory().getStatics().freeMemoryForTests(); + } + + /** + * Informs WebView of the network state. This is used to set + * the JavaScript property window.navigator.isOnline and + * generates the online/offline event as specified in HTML5, sec. 5.7.7 + * + * @param networkUp a boolean indicating if network is available + */ + public void setNetworkAvailable(boolean networkUp) { + checkThread(); + mProvider.setNetworkAvailable(networkUp); + } + + /** + * Saves the state of this WebView used in + * {@link android.app.Activity#onSaveInstanceState}. Please note that this + * method no longer stores the display data for this WebView. The previous + * behavior could potentially leak files if {@link #restoreState} was never + * called. + * + * @param outState the Bundle to store this WebView's state + * @return the same copy of the back/forward list used to save the state, {@code null} if the + * method fails. + */ + @Nullable + public WebBackForwardList saveState(Bundle outState) { + checkThread(); + return mProvider.saveState(outState); + } + + /** + * Saves the current display data to the Bundle given. Used in conjunction + * with {@link #saveState}. + * @param b a Bundle to store the display data + * @param dest the file to store the serialized picture data. Will be + * overwritten with this WebView's picture data. + * @return {@code true} if the picture was successfully saved + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public boolean savePicture(Bundle b, final File dest) { + checkThread(); + return mProvider.savePicture(b, dest); + } + + /** + * Restores the display data that was saved in {@link #savePicture}. Used in + * conjunction with {@link #restoreState}. Note that this will not work if + * this WebView is hardware accelerated. + * + * @param b a Bundle containing the saved display data + * @param src the file where the picture data was stored + * @return {@code true} if the picture was successfully restored + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public boolean restorePicture(Bundle b, File src) { + checkThread(); + return mProvider.restorePicture(b, src); + } + + /** + * Restores the state of this WebView from the given Bundle. This method is + * intended for use in {@link android.app.Activity#onRestoreInstanceState} + * and should be called to restore the state of this WebView. If + * it is called after this WebView has had a chance to build state (load + * pages, create a back/forward list, etc.) there may be undesirable + * side-effects. Please note that this method no longer restores the + * display data for this WebView. + * + * @param inState the incoming Bundle of state + * @return the restored back/forward list or {@code null} if restoreState failed + */ + @Nullable + public WebBackForwardList restoreState(Bundle inState) { + checkThread(); + return mProvider.restoreState(inState); + } + + /** + * Loads the given URL with the specified additional HTTP headers. + * <p> + * Also see compatibility note on {@link #evaluateJavascript}. + * + * @param url the URL of the resource to load + * @param additionalHttpHeaders the additional headers to be used in the + * HTTP request for this URL, specified as a map from name to + * value. Note that if this map contains any of the headers + * that are set by default by this WebView, such as those + * controlling caching, accept types or the User-Agent, their + * values may be overridden by this WebView's defaults. + */ + public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { + checkThread(); + mProvider.loadUrl(url, additionalHttpHeaders); + } + + /** + * Loads the given URL. + * <p> + * Also see compatibility note on {@link #evaluateJavascript}. + * + * @param url the URL of the resource to load + */ public void loadUrl(String url) { + checkThread(); + mProvider.loadUrl(url); + } + + /** + * Loads the URL with postData using "POST" method into this WebView. If url + * is not a network URL, it will be loaded with {@link #loadUrl(String)} + * instead, ignoring the postData param. + * + * @param url the URL of the resource to load + * @param postData the data will be passed to "POST" request, which must be + * be "application/x-www-form-urlencoded" encoded. + */ + public void postUrl(String url, byte[] postData) { + checkThread(); + if (URLUtil.isNetworkUrl(url)) { + mProvider.postUrl(url, postData); + } else { + mProvider.loadUrl(url); + } } - public void loadData(String data, String mimeType, String encoding) { + /** + * Loads the given data into this WebView using a 'data' scheme URL. + * <p> + * Note that JavaScript's same origin policy means that script running in a + * page loaded using this method will be unable to access content loaded + * using any scheme other than 'data', including 'http(s)'. To avoid this + * restriction, use {@link + * #loadDataWithBaseURL(String,String,String,String,String) + * loadDataWithBaseURL()} with an appropriate base URL. + * <p> + * The encoding parameter specifies whether the data is base64 or URL + * encoded. If the data is base64 encoded, the value of the encoding + * parameter must be 'base64'. For all other values of the parameter, + * including {@code null}, it is assumed that the data uses ASCII encoding for + * octets inside the range of safe URL characters and use the standard %xx + * hex encoding of URLs for octets outside that range. For example, '#', + * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively. + * <p> + * The 'data' scheme URL formed by this method uses the default US-ASCII + * charset. If you need need to set a different charset, you should form a + * 'data' scheme URL which explicitly specifies a charset parameter in the + * mediatype portion of the URL and call {@link #loadUrl(String)} instead. + * Note that the charset obtained from the mediatype portion of a data URL + * always overrides that specified in the HTML or XML document itself. + * + * @param data a String of data in the given encoding + * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null}, + * defaults to 'text/html'. + * @param encoding the encoding of the data + */ + public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) { + checkThread(); + mProvider.loadData(data, mimeType, encoding); } - public void loadDataWithBaseURL(String baseUrl, String data, - String mimeType, String encoding, String failUrl) { + /** + * Loads the given data into this WebView, using baseUrl as the base URL for + * the content. The base URL is used both to resolve relative URLs and when + * applying JavaScript's same origin policy. The historyUrl is used for the + * history entry. + * <p> + * Note that content specified in this way can access local device files + * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than + * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'. + * <p> + * If the base URL uses the data scheme, this method is equivalent to + * calling {@link #loadData(String,String,String) loadData()} and the + * historyUrl is ignored, and the data will be treated as part of a data: URL. + * If the base URL uses any other scheme, then the data will be loaded into + * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded + * entities in the string will not be decoded. + * <p> + * Note that the baseUrl is sent in the 'Referer' HTTP header when + * requesting subresources (images, etc.) of the page loaded using this method. + * + * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to + * 'about:blank'. + * @param data a String of data in the given encoding + * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null}, + * defaults to 'text/html'. + * @param encoding the encoding of the data + * @param historyUrl the URL to use as the history entry. If {@code null} defaults + * to 'about:blank'. If non-null, this must be a valid URL. + */ + public void loadDataWithBaseURL(@Nullable String baseUrl, String data, + @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) { + checkThread(); + mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); } + /** + * Asynchronously evaluates JavaScript in the context of the currently displayed page. + * If non-null, |resultCallback| will be invoked with any result returned from that + * execution. This method must be called on the UI thread and the callback will + * be made on the UI thread. + * <p> + * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or + * later, JavaScript state from an empty WebView is no longer persisted across navigations like + * {@link #loadUrl(String)}. For example, global variables and functions defined before calling + * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use + * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations. + * + * @param script the JavaScript to execute. + * @param resultCallback A callback to be invoked when the script execution + * completes with the result of the execution (if any). + * May be {@code null} if no notification of the result is required. + */ + public void evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback) { + checkThread(); + mProvider.evaluateJavaScript(script, resultCallback); + } + + /** + * Saves the current view as a web archive. + * + * @param filename the filename where the archive should be placed + */ + public void saveWebArchive(String filename) { + checkThread(); + mProvider.saveWebArchive(filename); + } + + /** + * Saves the current view as a web archive. + * + * @param basename the filename where the archive should be placed + * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename + * is assumed to be a directory in which a filename will be + * chosen according to the URL of the current page. + * @param callback called after the web archive has been saved. The + * parameter for onReceiveValue will either be the filename + * under which the file was saved, or {@code null} if saving the + * file failed. + */ + public void saveWebArchive(String basename, boolean autoname, @Nullable ValueCallback<String> + callback) { + checkThread(); + mProvider.saveWebArchive(basename, autoname, callback); + } + + /** + * Stops the current load. + */ public void stopLoading() { + checkThread(); + mProvider.stopLoading(); } + /** + * Reloads the current URL. + */ public void reload() { + checkThread(); + mProvider.reload(); } + /** + * Gets whether this WebView has a back history item. + * + * @return {@code true} if this WebView has a back history item + */ public boolean canGoBack() { - return false; + checkThread(); + return mProvider.canGoBack(); } + /** + * Goes back in the history of this WebView. + */ public void goBack() { + checkThread(); + mProvider.goBack(); } + /** + * Gets whether this WebView has a forward history item. + * + * @return {@code true} if this WebView has a forward history item + */ public boolean canGoForward() { - return false; + checkThread(); + return mProvider.canGoForward(); } + /** + * Goes forward in the history of this WebView. + */ public void goForward() { + checkThread(); + mProvider.goForward(); } + /** + * Gets whether the page can go back or forward the given + * number of steps. + * + * @param steps the negative or positive number of steps to move the + * history + */ public boolean canGoBackOrForward(int steps) { - return false; + checkThread(); + return mProvider.canGoBackOrForward(steps); } + /** + * Goes to the history item that is the number of steps away from + * the current item. Steps is negative if backward and positive + * if forward. + * + * @param steps the number of steps to take back or forward in the back + * forward list + */ public void goBackOrForward(int steps) { + checkThread(); + mProvider.goBackOrForward(steps); + } + + /** + * Gets whether private browsing is enabled in this WebView. + */ + public boolean isPrivateBrowsingEnabled() { + checkThread(); + return mProvider.isPrivateBrowsingEnabled(); } + /** + * Scrolls the contents of this WebView up by half the view size. + * + * @param top {@code true} to jump to the top of the page + * @return {@code true} if the page was scrolled + */ public boolean pageUp(boolean top) { - return false; + checkThread(); + return mProvider.pageUp(top); } - + + /** + * Scrolls the contents of this WebView down by half the page size. + * + * @param bottom {@code true} to jump to bottom of page + * @return {@code true} if the page was scrolled + */ public boolean pageDown(boolean bottom) { - return false; + checkThread(); + return mProvider.pageDown(bottom); } + /** + * Posts a {@link VisualStateCallback}, which will be called when + * the current state of the WebView is ready to be drawn. + * + * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not + * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The + * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of + * the DOM at the current time are ready to be drawn the next time the {@link WebView} + * draws. + * + * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the + * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also + * contain updates applied after the callback was posted. + * + * <p>The state of the DOM covered by this API includes the following: + * <ul> + * <li>primitive HTML elements (div, img, span, etc..)</li> + * <li>images</li> + * <li>CSS animations</li> + * <li>WebGL</li> + * <li>canvas</li> + * </ul> + * It does not include the state of: + * <ul> + * <li>the video tag</li> + * </ul> + * + * <p>To guarantee that the {@link WebView} will successfully render the first frame + * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions + * must be met: + * <ul> + * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then + * the {@link WebView} must be attached to the view hierarchy.</li> + * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE} + * then the {@link WebView} must be attached to the view hierarchy and must be made + * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li> + * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the + * {@link WebView} must be attached to the view hierarchy and its + * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed + * values and must be made {@link View#VISIBLE VISIBLE} from the + * {@link VisualStateCallback#onComplete} method.</li> + * </ul> + * + * <p>When using this API it is also recommended to enable pre-rasterization if the {@link + * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for + * more details and do consider its caveats. + * + * @param requestId An id that will be returned in the callback to allow callers to match + * requests with callbacks. + * @param callback The callback to be invoked. + */ + public void postVisualStateCallback(long requestId, VisualStateCallback callback) { + checkThread(); + mProvider.insertVisualStateCallback(requestId, callback); + } + + /** + * Clears this WebView so that onDraw() will draw nothing but white background, + * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY. + * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state + * and release page resources (including any running JavaScript). + */ + @Deprecated public void clearView() { + checkThread(); + mProvider.clearView(); } - + + /** + * Gets a new picture that captures the current contents of this WebView. + * The picture is of the entire document being displayed, and is not + * limited to the area currently displayed by this WebView. Also, the + * picture is a static copy and is unaffected by later changes to the + * content being displayed. + * <p> + * Note that due to internal changes, for API levels between + * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and + * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the + * picture does not include fixed position elements or scrollable divs. + * <p> + * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture + * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve + * additional conversion at a cost in memory and performance. Also the + * {@link android.graphics.Picture#createFromStream} and + * {@link android.graphics.Picture#writeToStream} methods are not supported on the + * returned object. + * + * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or + * {@link #saveWebArchive} to save the content to a file. + * + * @return a picture that captures the current contents of this WebView + */ + @Deprecated public Picture capturePicture() { - return null; + checkThread(); + return mProvider.capturePicture(); + } + + /** + * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user + * to provide a print document name. + */ + @Deprecated + public PrintDocumentAdapter createPrintDocumentAdapter() { + checkThread(); + return mProvider.createPrintDocumentAdapter("default"); } + /** + * Creates a PrintDocumentAdapter that provides the content of this WebView for printing. + * + * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot + * be drawn during the conversion process - any such draws are undefined. It is recommended + * to use a dedicated off screen WebView for the printing. If necessary, an application may + * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance + * wrapped around the object returned and observing the onStart and onFinish methods. See + * {@link android.print.PrintDocumentAdapter} for more information. + * + * @param documentName The user-facing name of the printed document. See + * {@link android.print.PrintDocumentInfo} + */ + public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) { + checkThread(); + return mProvider.createPrintDocumentAdapter(documentName); + } + + /** + * Gets the current scale of this WebView. + * + * @return the current scale + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. + */ + @Deprecated + @ViewDebug.ExportedProperty(category = "webview") public float getScale() { - return 0; + checkThread(); + return mProvider.getScale(); } + /** + * Sets the initial scale for this WebView. 0 means default. + * The behavior for the default scale depends on the state of + * {@link WebSettings#getUseWideViewPort()} and + * {@link WebSettings#getLoadWithOverviewMode()}. + * If the content fits into the WebView control by width, then + * the zoom is set to 100%. For wide content, the behavior + * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}. + * If its value is {@code true}, the content will be zoomed out to be fit + * by width into the WebView control, otherwise not. + * + * If initial scale is greater than 0, WebView starts with this value + * as initial scale. + * Please note that unlike the scale properties in the viewport meta tag, + * this method doesn't take the screen density into account. + * + * @param scaleInPercent the initial scale in percent + */ public void setInitialScale(int scaleInPercent) { + checkThread(); + mProvider.setInitialScale(scaleInPercent); } + /** + * Invokes the graphical zoom picker widget for this WebView. This will + * result in the zoom widget appearing on the screen to control the zoom + * level of this WebView. + */ public void invokeZoomPicker() { + checkThread(); + mProvider.invokeZoomPicker(); + } + + /** + * Gets a HitTestResult based on the current cursor node. If a HTML::a + * tag is found and the anchor has a non-JavaScript URL, the HitTestResult + * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field. + * If the anchor does not have a URL or if it is a JavaScript URL, the type + * will be UNKNOWN_TYPE and the URL has to be retrieved through + * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is + * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in + * the "extra" field. A type of + * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as + * a child node. If a phone number is found, the HitTestResult type is set + * to PHONE_TYPE and the phone number is set in the "extra" field of + * HitTestResult. If a map address is found, the HitTestResult type is set + * to GEO_TYPE and the address is set in the "extra" field of HitTestResult. + * If an email address is found, the HitTestResult type is set to EMAIL_TYPE + * and the email is set in the "extra" field of HitTestResult. Otherwise, + * HitTestResult type is set to UNKNOWN_TYPE. + */ + public HitTestResult getHitTestResult() { + checkThread(); + return mProvider.getHitTestResult(); } - public void requestFocusNodeHref(Message hrefMsg) { + /** + * Requests the anchor or image element URL at the last tapped point. + * If hrefMsg is {@code null}, this method returns immediately and does not + * dispatch hrefMsg to its target. If the tapped point hits an image, + * an anchor, or an image in an anchor, the message associates + * strings in named keys in its data. The value paired with the key + * may be an empty string. + * + * @param hrefMsg the message to be dispatched with the result of the + * request. The message data contains three keys. "url" + * returns the anchor's href attribute. "title" returns the + * anchor's text. "src" returns the image's src attribute. + */ + public void requestFocusNodeHref(@Nullable Message hrefMsg) { + checkThread(); + mProvider.requestFocusNodeHref(hrefMsg); } + /** + * Requests the URL of the image last touched by the user. msg will be sent + * to its target with a String representing the URL as its object. + * + * @param msg the message to be dispatched with the result of the request + * as the data member with "url" as key. The result can be {@code null}. + */ public void requestImageRef(Message msg) { + checkThread(); + mProvider.requestImageRef(msg); } + /** + * Gets the URL for the current page. This is not always the same as the URL + * passed to WebViewClient.onPageStarted because although the load for + * that URL has begun, the current page may not have changed. + * + * @return the URL for the current page + */ + @ViewDebug.ExportedProperty(category = "webview") public String getUrl() { - return null; + checkThread(); + return mProvider.getUrl(); } + /** + * Gets the original URL for the current page. This is not always the same + * as the URL passed to WebViewClient.onPageStarted because although the + * load for that URL has begun, the current page may not have changed. + * Also, there may have been redirects resulting in a different URL to that + * originally requested. + * + * @return the URL that was originally requested for the current page + */ + @ViewDebug.ExportedProperty(category = "webview") + public String getOriginalUrl() { + checkThread(); + return mProvider.getOriginalUrl(); + } + + /** + * Gets the title for the current page. This is the title of the current page + * until WebViewClient.onReceivedTitle is called. + * + * @return the title for the current page + */ + @ViewDebug.ExportedProperty(category = "webview") public String getTitle() { - return null; + checkThread(); + return mProvider.getTitle(); } + /** + * Gets the favicon for the current page. This is the favicon of the current + * page until WebViewClient.onReceivedIcon is called. + * + * @return the favicon for the current page + */ public Bitmap getFavicon() { - return null; + checkThread(); + return mProvider.getFavicon(); } + /** + * Gets the touch icon URL for the apple-touch-icon <link> element, or + * a URL on this site's server pointing to the standard location of a + * touch icon. + * + * @hide + */ + public String getTouchIconUrl() { + return mProvider.getTouchIconUrl(); + } + + /** + * Gets the progress for the current page. + * + * @return the progress for the current page between 0 and 100 + */ public int getProgress() { - return 0; + checkThread(); + return mProvider.getProgress(); } - + + /** + * Gets the height of the HTML content. + * + * @return the height of the HTML content + */ + @ViewDebug.ExportedProperty(category = "webview") public int getContentHeight() { - return 0; + checkThread(); + return mProvider.getContentHeight(); + } + + /** + * Gets the width of the HTML content. + * + * @return the width of the HTML content + * @hide + */ + @ViewDebug.ExportedProperty(category = "webview") + public int getContentWidth() { + return mProvider.getContentWidth(); } + /** + * Pauses all layout, parsing, and JavaScript timers for all WebViews. This + * is a global requests, not restricted to just this WebView. This can be + * useful if the application has been paused. + */ public void pauseTimers() { + checkThread(); + mProvider.pauseTimers(); } + /** + * Resumes all layout, parsing, and JavaScript timers for all WebViews. + * This will resume dispatching all timers. + */ public void resumeTimers() { + checkThread(); + mProvider.resumeTimers(); + } + + /** + * Does a best-effort attempt to pause any processing that can be paused + * safely, such as animations and geolocation. Note that this call + * does not pause JavaScript. To pause JavaScript globally, use + * {@link #pauseTimers}. + * + * To resume WebView, call {@link #onResume}. + */ + public void onPause() { + checkThread(); + mProvider.onPause(); + } + + /** + * Resumes a WebView after a previous call to {@link #onPause}. + */ + public void onResume() { + checkThread(); + mProvider.onResume(); + } + + /** + * Gets whether this WebView is paused, meaning onPause() was called. + * Calling onResume() sets the paused state back to {@code false}. + * + * @hide + */ + public boolean isPaused() { + return mProvider.isPaused(); + } + + /** + * Informs this WebView that memory is low so that it can free any available + * memory. + * @deprecated Memory caches are automatically dropped when no longer needed, and in response + * to system memory pressure. + */ + @Deprecated + public void freeMemory() { + checkThread(); + mProvider.freeMemory(); } - public void clearCache() { + /** + * Clears the resource cache. Note that the cache is per-application, so + * this will clear the cache for all WebViews used. + * + * @param includeDiskFiles if {@code false}, only the RAM cache is cleared + */ + public void clearCache(boolean includeDiskFiles) { + checkThread(); + mProvider.clearCache(includeDiskFiles); } + /** + * Removes the autocomplete popup from the currently focused form field, if + * present. Note this only affects the display of the autocomplete popup, + * it does not remove any saved form data from this WebView's store. To do + * that, use {@link WebViewDatabase#clearFormData}. + */ public void clearFormData() { + checkThread(); + mProvider.clearFormData(); } + /** + * Tells this WebView to clear its internal back/forward list. + */ public void clearHistory() { + checkThread(); + mProvider.clearHistory(); } + /** + * Clears the SSL preferences table stored in response to proceeding with + * SSL certificate errors. + */ public void clearSslPreferences() { + checkThread(); + mProvider.clearSslPreferences(); + } + + /** + * Clears the client certificate preferences stored in response + * to proceeding/cancelling client cert requests. Note that WebView + * automatically clears these preferences when it receives a + * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are + * shared by all the WebViews that are created by the embedder application. + * + * @param onCleared A runnable to be invoked when client certs are cleared. + * The runnable will be called in UI thread. + */ + public static void clearClientCertPreferences(@Nullable Runnable onCleared) { + getFactory().getStatics().clearClientCertPreferences(onCleared); + } + + /** + * Starts Safe Browsing initialization. + * <p> + * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is + * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those + * devices {@code callback} will receive {@code false}. + * <p> + * This does not enable the Safe Browsing feature itself, and should only be called if Safe + * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This + * prepares resources used for Safe Browsing. + * <p> + * This should be called with the Application Context (and will always use the Application + * context to do its work regardless). + * + * @param context Application Context. + * @param callback will be called on the UI thread with {@code true} if initialization is + * successful, {@code false} otherwise. + */ + public static void startSafeBrowsing(Context context, + @Nullable ValueCallback<Boolean> callback) { + getFactory().getStatics().initSafeBrowsing(context, callback); + } + + /** + * Sets the list of domains that are exempt from SafeBrowsing checks. The list is + * global for all the WebViews. + * <p> + * Each rule should take one of these: + * <table> + * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr> + * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr> + * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr> + * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr> + * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr> + * </table> + * <p> + * All other rules, including wildcards, are invalid. + * + * @param urls the list of URLs + * @param callback will be called with {@code true} if URLs are successfully added to the + * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will + * be run on the UI thread + */ + public static void setSafeBrowsingWhitelist(@NonNull List<String> urls, + @Nullable ValueCallback<Boolean> callback) { + getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback); + } + + /** + * Returns a URL pointing to the privacy policy for Safe Browsing reporting. + * + * @return the url pointing to a privacy policy document which can be displayed to users. + */ + @NonNull + public static Uri getSafeBrowsingPrivacyPolicyUrl() { + return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl(); + } + + /** + * Gets the WebBackForwardList for this WebView. This contains the + * back/forward list for use in querying each item in the history stack. + * This is a copy of the private WebBackForwardList so it contains only a + * snapshot of the current state. Multiple calls to this method may return + * different objects. The object returned from this method will not be + * updated to reflect any new state. + */ + public WebBackForwardList copyBackForwardList() { + checkThread(); + return mProvider.copyBackForwardList(); + + } + + /** + * Registers the listener to be notified as find-on-page operations + * progress. This will replace the current listener. + * + * @param listener an implementation of {@link FindListener} + */ + public void setFindListener(FindListener listener) { + checkThread(); + setupFindListenerIfNeeded(); + mFindListener.mUserFindListener = listener; + } + + /** + * Highlights and scrolls to the next match found by + * {@link #findAllAsync}, wrapping around page boundaries as necessary. + * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)} + * has not been called yet, or if {@link #clearMatches} has been called since the + * last find operation, this function does nothing. + * + * @param forward the direction to search + * @see #setFindListener + */ + public void findNext(boolean forward) { + checkThread(); + mProvider.findNext(forward); + } + + /** + * Finds all instances of find on the page and highlights them. + * Notifies any registered {@link FindListener}. + * + * @param find the string to find + * @return the number of occurrences of the String "find" that were found + * @deprecated {@link #findAllAsync} is preferred. + * @see #setFindListener + */ + @Deprecated + public int findAll(String find) { + checkThread(); + StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync"); + return mProvider.findAll(find); + } + + /** + * Finds all instances of find on the page and highlights them, + * asynchronously. Notifies any registered {@link FindListener}. + * Successive calls to this will cancel any pending searches. + * + * @param find the string to find. + * @see #setFindListener + */ + public void findAllAsync(String find) { + checkThread(); + mProvider.findAllAsync(find); + } + + /** + * Starts an ActionMode for finding text in this WebView. Only works if this + * WebView is attached to the view system. + * + * @param text if non-null, will be the initial text to search for. + * Otherwise, the last String searched for in this WebView will + * be used to start. + * @param showIme if {@code true}, show the IME, assuming the user will begin typing. + * If {@code false} and text is non-null, perform a find all. + * @return {@code true} if the find dialog is shown, {@code false} otherwise + * @deprecated This method does not work reliably on all Android versions; + * implementing a custom find dialog using WebView.findAllAsync() + * provides a more robust solution. + */ + @Deprecated + public boolean showFindDialog(@Nullable String text, boolean showIme) { + checkThread(); + return mProvider.showFindDialog(text, showIme); } + /** + * Gets the first substring consisting of the address of a physical + * location. Currently, only addresses in the United States are detected, + * and consist of: + * <ul> + * <li>a house number</li> + * <li>a street name</li> + * <li>a street type (Road, Circle, etc), either spelled out or + * abbreviated</li> + * <li>a city name</li> + * <li>a state or territory, either spelled out or two-letter abbr</li> + * <li>an optional 5 digit or 9 digit zip code</li> + * </ul> + * All names must be correctly capitalized, and the zip code, if present, + * must be valid for the state. The street type must be a standard USPS + * spelling or abbreviation. The state or territory must also be spelled + * or abbreviated using USPS standards. The house number may not exceed + * five digits. + * + * @param addr the string to search for addresses + * @return the address, or if no address is found, {@code null} + */ + @Nullable public static String findAddress(String addr) { - return null; + // TODO: Rewrite this in Java so it is not needed to start up chromium + // Could also be deprecated + return getFactory().getStatics().findAddress(addr); + } + + /** + * For apps targeting the L release, WebView has a new default behavior that reduces + * memory footprint and increases performance by intelligently choosing + * the portion of the HTML document that needs to be drawn. These + * optimizations are transparent to the developers. However, under certain + * circumstances, an App developer may want to disable them: + * <ol> + * <li>When an app uses {@link #onDraw} to do own drawing and accesses portions + * of the page that is way outside the visible portion of the page.</li> + * <li>When an app uses {@link #capturePicture} to capture a very large HTML document. + * Note that capturePicture is a deprecated API.</li> + * </ol> + * Enabling drawing the entire HTML document has a significant performance + * cost. This method should be called before any WebViews are created. + */ + public static void enableSlowWholeDocumentDraw() { + getFactory().getStatics().enableSlowWholeDocumentDraw(); } + /** + * Clears the highlighting surrounding text matches created by + * {@link #findAllAsync}. + */ + public void clearMatches() { + checkThread(); + mProvider.clearMatches(); + } + + /** + * Queries the document to see if it contains any image references. The + * message object will be dispatched with arg1 being set to 1 if images + * were found and 0 if the document does not reference any images. + * + * @param response the message that will be dispatched with the result + */ public void documentHasImages(Message response) { + checkThread(); + mProvider.documentHasImages(response); } + /** + * Sets the WebViewClient that will receive various notifications and + * requests. This will replace the current handler. + * + * @param client an implementation of WebViewClient + * @see #getWebViewClient + */ public void setWebViewClient(WebViewClient client) { + checkThread(); + mProvider.setWebViewClient(client); + } + + /** + * Gets the WebViewClient. + * + * @return the WebViewClient, or a default client if not yet set + * @see #setWebViewClient + */ + public WebViewClient getWebViewClient() { + checkThread(); + return mProvider.getWebViewClient(); } + /** + * Registers the interface to be used when content can not be handled by + * the rendering engine, and should be downloaded instead. This will replace + * the current handler. + * + * @param listener an implementation of DownloadListener + */ public void setDownloadListener(DownloadListener listener) { + checkThread(); + mProvider.setDownloadListener(listener); } + /** + * Sets the chrome handler. This is an implementation of WebChromeClient for + * use in handling JavaScript dialogs, favicons, titles, and the progress. + * This will replace the current handler. + * + * @param client an implementation of WebChromeClient + * @see #getWebChromeClient + */ public void setWebChromeClient(WebChromeClient client) { + checkThread(); + mProvider.setWebChromeClient(client); } - public void addJavascriptInterface(Object obj, String interfaceName) { + /** + * Gets the chrome handler. + * + * @return the WebChromeClient, or {@code null} if not yet set + * @see #setWebChromeClient + */ + @Nullable + public WebChromeClient getWebChromeClient() { + checkThread(); + return mProvider.getWebChromeClient(); + } + + /** + * Sets the Picture listener. This is an interface used to receive + * notifications of a new Picture. + * + * @param listener an implementation of WebView.PictureListener + * @deprecated This method is now obsolete. + */ + @Deprecated + public void setPictureListener(PictureListener listener) { + checkThread(); + mProvider.setPictureListener(listener); + } + + /** + * Injects the supplied Java object into this WebView. The object is + * injected into the JavaScript context of the main frame, using the + * supplied name. This allows the Java object's methods to be + * accessed from JavaScript. For applications targeted to API + * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + * and above, only public methods that are annotated with + * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript. + * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below, + * all public methods (including the inherited ones) can be accessed, see the + * important security note below for implications. + * <p> Note that injected objects will not appear in JavaScript until the page is next + * (re)loaded. JavaScript should be enabled before injecting the object. For example: + * <pre> + * class JsObject { + * {@literal @}JavascriptInterface + * public String toString() { return "injectedObject"; } + * } + * webview.getSettings().setJavaScriptEnabled(true); + * webView.addJavascriptInterface(new JsObject(), "injectedObject"); + * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null); + * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre> + * <p> + * <strong>IMPORTANT:</strong> + * <ul> + * <li> This method can be used to allow JavaScript to control the host + * application. This is a powerful feature, but also presents a security + * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier. + * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN} + * are still vulnerable if the app runs on a device running Android earlier than 4.2. + * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + * and to ensure the method is called only when running on Android 4.2 or later. + * With these older versions, JavaScript could use reflection to access an + * injected object's public fields. Use of this method in a WebView + * containing untrusted content could allow an attacker to manipulate the + * host application in unintended ways, executing Java code with the + * permissions of the host application. Use extreme care when using this + * method in a WebView which could contain untrusted content.</li> + * <li> JavaScript interacts with Java object on a private, background + * thread of this WebView. Care is therefore required to maintain thread + * safety. + * </li> + * <li> The Java object's fields are not accessible.</li> + * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP} + * and above, methods of injected Java objects are enumerable from + * JavaScript.</li> + * </ul> + * + * @param object the Java object to inject into this WebView's JavaScript + * context. {@code null} values are ignored. + * @param name the name used to expose the object in JavaScript + */ + public void addJavascriptInterface(Object object, String name) { + checkThread(); + mProvider.addJavascriptInterface(object, name); + } + + /** + * Removes a previously injected Java object from this WebView. Note that + * the removal will not be reflected in JavaScript until the page is next + * (re)loaded. See {@link #addJavascriptInterface}. + * + * @param name the name used to expose the object in JavaScript + */ + public void removeJavascriptInterface(@NonNull String name) { + checkThread(); + mProvider.removeJavascriptInterface(name); + } + + /** + * Creates a message channel to communicate with JS and returns the message + * ports that represent the endpoints of this message channel. The HTML5 message + * channel functionality is described + * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here + * </a> + * + * <p>The returned message channels are entangled and already in started state. + * + * @return the two message ports that form the message channel. + */ + public WebMessagePort[] createWebMessageChannel() { + checkThread(); + return mProvider.createWebMessageChannel(); + } + + /** + * Post a message to main frame. The embedded application can restrict the + * messages to a certain target origin. See + * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages"> + * HTML5 spec</a> for how target origin can be used. + * <p> + * A target origin can be set as a wildcard ("*"). However this is not recommended. + * See the page above for security issues. + * + * @param message the WebMessage + * @param targetOrigin the target origin. + */ + public void postWebMessage(WebMessage message, Uri targetOrigin) { + checkThread(); + mProvider.postMessageToMainFrame(message, targetOrigin); + } + + /** + * Gets the WebSettings object used to control the settings for this + * WebView. + * + * @return a WebSettings object that can be used to control this WebView's + * settings + */ + public WebSettings getSettings() { + checkThread(); + return mProvider.getSettings(); + } + + /** + * Enables debugging of web contents (HTML / CSS / JavaScript) + * loaded into any WebViews of this application. This flag can be enabled + * in order to facilitate debugging of web layouts and JavaScript + * code running inside WebViews. Please refer to WebView documentation + * for the debugging guide. + * + * The default is {@code false}. + * + * @param enabled whether to enable web contents debugging + */ + public static void setWebContentsDebuggingEnabled(boolean enabled) { + getFactory().getStatics().setWebContentsDebuggingEnabled(enabled); + } + + /** + * Gets the list of currently loaded plugins. + * + * @return the list of currently loaded plugins + * @deprecated This was used for Gears, which has been deprecated. + * @hide + */ + @Deprecated + public static synchronized PluginList getPluginList() { + return new PluginList(); + } + + /** + * @deprecated This was used for Gears, which has been deprecated. + * @hide + */ + @Deprecated + public void refreshPlugins(boolean reloadOpenPages) { + checkThread(); + } + + /** + * Puts this WebView into text selection mode. Do not rely on this + * functionality; it will be deprecated in the future. + * + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public void emulateShiftHeld() { + checkThread(); + } + + /** + * @deprecated WebView no longer needs to implement + * ViewGroup.OnHierarchyChangeListener. This method does nothing now. + */ + @Override + // Cannot add @hide as this can always be accessed via the interface. + @Deprecated + public void onChildViewAdded(View parent, View child) {} + + /** + * @deprecated WebView no longer needs to implement + * ViewGroup.OnHierarchyChangeListener. This method does nothing now. + */ + @Override + // Cannot add @hide as this can always be accessed via the interface. + @Deprecated + public void onChildViewRemoved(View p, View child) {} + + /** + * @deprecated WebView should not have implemented + * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now. + */ + @Override + // Cannot add @hide as this can always be accessed via the interface. + @Deprecated + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + } + + /** + * @deprecated Only the default case, {@code true}, will be supported in a future version. + */ + @Deprecated + public void setMapTrackballToArrowKeys(boolean setMap) { + checkThread(); + mProvider.setMapTrackballToArrowKeys(setMap); } + + public void flingScroll(int vx, int vy) { + checkThread(); + mProvider.flingScroll(vx, vy); + } + + /** + * Gets the zoom controls for this WebView, as a separate View. The caller + * is responsible for inserting this View into the layout hierarchy. + * <p/> + * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced + * built-in zoom mechanisms for the WebView, as opposed to these separate + * zoom controls. The built-in mechanisms are preferred and can be enabled + * using {@link WebSettings#setBuiltInZoomControls}. + * + * @deprecated the built-in zoom mechanisms are preferred + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} + */ + @Deprecated public View getZoomControls() { - return null; + checkThread(); + return mProvider.getZoomControls(); } + /** + * Gets whether this WebView can be zoomed in. + * + * @return {@code true} if this WebView can be zoomed in + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. + */ + @Deprecated + public boolean canZoomIn() { + checkThread(); + return mProvider.canZoomIn(); + } + + /** + * Gets whether this WebView can be zoomed out. + * + * @return {@code true} if this WebView can be zoomed out + * + * @deprecated This method is prone to inaccuracy due to race conditions + * between the web rendering and UI threads; prefer + * {@link WebViewClient#onScaleChanged}. + */ + @Deprecated + public boolean canZoomOut() { + checkThread(); + return mProvider.canZoomOut(); + } + + /** + * Performs a zoom operation in this WebView. + * + * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's + * zoom limits. This value must be in the range 0.01 to 100.0 inclusive. + */ + public void zoomBy(float zoomFactor) { + checkThread(); + if (zoomFactor < 0.01) + throw new IllegalArgumentException("zoomFactor must be greater than 0.01."); + if (zoomFactor > 100.0) + throw new IllegalArgumentException("zoomFactor must be less than 100."); + mProvider.zoomBy(zoomFactor); + } + + /** + * Performs zoom in in this WebView. + * + * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes + */ public boolean zoomIn() { - return false; + checkThread(); + return mProvider.zoomIn(); } + /** + * Performs zoom out in this WebView. + * + * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes + */ public boolean zoomOut() { - return false; + checkThread(); + return mProvider.zoomOut(); + } + + /** + * @deprecated This method is now obsolete. + * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} + */ + @Deprecated + public void debugDump() { + checkThread(); + } + + /** + * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)} + * @hide + */ + @Override + public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) { + mProvider.dumpViewHierarchyWithProperties(out, level); + } + + /** + * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)} + * @hide + */ + @Override + public View findHierarchyView(String className, int hashCode) { + return mProvider.findHierarchyView(className, hashCode); + } + + /** @hide */ + @IntDef({ + RENDERER_PRIORITY_WAIVED, + RENDERER_PRIORITY_BOUND, + RENDERER_PRIORITY_IMPORTANT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RendererPriority {} + + /** + * The renderer associated with this WebView is bound with + * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level + * {@link WebView} renderers will be strong targets for out of memory + * killing. + * + * Use with {@link #setRendererPriorityPolicy}. + */ + public static final int RENDERER_PRIORITY_WAIVED = 0; + /** + * The renderer associated with this WebView is bound with + * the default priority for services. + * + * Use with {@link #setRendererPriorityPolicy}. + */ + public static final int RENDERER_PRIORITY_BOUND = 1; + /** + * The renderer associated with this WebView is bound with + * {@link Context#BIND_IMPORTANT}. + * + * Use with {@link #setRendererPriorityPolicy}. + */ + public static final int RENDERER_PRIORITY_IMPORTANT = 2; + + /** + * Set the renderer priority policy for this {@link WebView}. The + * priority policy will be used to determine whether an out of + * process renderer should be considered to be a target for OOM + * killing. + * + * Because a renderer can be associated with more than one + * WebView, the final priority it is computed as the maximum of + * any attached WebViews. When a WebView is destroyed it will + * cease to be considerered when calculating the renderer + * priority. Once no WebViews remain associated with the renderer, + * the priority of the renderer will be reduced to + * {@link #RENDERER_PRIORITY_WAIVED}. + * + * The default policy is to set the priority to + * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility, + * and this should not be changed unless the caller also handles + * renderer crashes with + * {@link WebViewClient#onRenderProcessGone}. Any other setting + * will result in WebView renderers being killed by the system + * more aggressively than the application. + * + * @param rendererRequestedPriority the minimum priority at which + * this WebView desires the renderer process to be bound. + * @param waivedWhenNotVisible if {@code true}, this flag specifies that + * when this WebView is not visible, it will be treated as + * if it had requested a priority of + * {@link #RENDERER_PRIORITY_WAIVED}. + */ + public void setRendererPriorityPolicy( + @RendererPriority int rendererRequestedPriority, + boolean waivedWhenNotVisible) { + mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible); + } + + /** + * Get the requested renderer priority for this WebView. + * + * @return the requested renderer priority policy. + */ + @RendererPriority + public int getRendererRequestedPriority() { + return mProvider.getRendererRequestedPriority(); + } + + /** + * Return whether this WebView requests a priority of + * {@link #RENDERER_PRIORITY_WAIVED} when not visible. + * + * @return whether this WebView requests a priority of + * {@link #RENDERER_PRIORITY_WAIVED} when not visible. + */ + public boolean getRendererPriorityWaivedWhenNotVisible() { + return mProvider.getRendererPriorityWaivedWhenNotVisible(); + } + + /** + * Sets the {@link TextClassifier} for this WebView. + */ + public void setTextClassifier(@Nullable TextClassifier textClassifier) { + mProvider.setTextClassifier(textClassifier); + } + + /** + * Returns the {@link TextClassifier} used by this WebView. + * If no TextClassifier has been set, this WebView uses the default set by the system. + */ + @NonNull + public TextClassifier getTextClassifier() { + return mProvider.getTextClassifier(); + } + + //------------------------------------------------------------------------- + // Interface for WebView providers + //------------------------------------------------------------------------- + + /** + * Gets the WebViewProvider. Used by providers to obtain the underlying + * implementation, e.g. when the application responds to + * WebViewClient.onCreateWindow() request. + * + * @hide WebViewProvider is not public API. + */ + @SystemApi + public WebViewProvider getWebViewProvider() { + return mProvider; + } + + /** + * Callback interface, allows the provider implementation to access non-public methods + * and fields, and make super-class calls in this WebView instance. + * @hide Only for use by WebViewProvider implementations + */ + @SystemApi + public class PrivateAccess { + // ---- Access to super-class methods ---- + public int super_getScrollBarStyle() { + return WebView.super.getScrollBarStyle(); + } + + public void super_scrollTo(int scrollX, int scrollY) { + WebView.super.scrollTo(scrollX, scrollY); + } + + public void super_computeScroll() { + WebView.super.computeScroll(); + } + + public boolean super_onHoverEvent(MotionEvent event) { + return WebView.super.onHoverEvent(event); + } + + public boolean super_performAccessibilityAction(int action, Bundle arguments) { + return WebView.super.performAccessibilityActionInternal(action, arguments); + } + + public boolean super_performLongClick() { + return WebView.super.performLongClick(); + } + + public boolean super_setFrame(int left, int top, int right, int bottom) { + return WebView.super.setFrame(left, top, right, bottom); + } + + public boolean super_dispatchKeyEvent(KeyEvent event) { + return WebView.super.dispatchKeyEvent(event); + } + + public boolean super_onGenericMotionEvent(MotionEvent event) { + return WebView.super.onGenericMotionEvent(event); + } + + public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) { + return WebView.super.requestFocus(direction, previouslyFocusedRect); + } + + public void super_setLayoutParams(ViewGroup.LayoutParams params) { + WebView.super.setLayoutParams(params); + } + + public void super_startActivityForResult(Intent intent, int requestCode) { + WebView.super.startActivityForResult(intent, requestCode); + } + + // ---- Access to non-public methods ---- + public void overScrollBy(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent) { + WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, + maxOverScrollX, maxOverScrollY, isTouchEvent); + } + + public void awakenScrollBars(int duration) { + WebView.this.awakenScrollBars(duration); + } + + public void awakenScrollBars(int duration, boolean invalidate) { + WebView.this.awakenScrollBars(duration, invalidate); + } + + public float getVerticalScrollFactor() { + return WebView.this.getVerticalScrollFactor(); + } + + public float getHorizontalScrollFactor() { + return WebView.this.getHorizontalScrollFactor(); + } + + public void setMeasuredDimension(int measuredWidth, int measuredHeight) { + WebView.this.setMeasuredDimension(measuredWidth, measuredHeight); + } + + public void onScrollChanged(int l, int t, int oldl, int oldt) { + WebView.this.onScrollChanged(l, t, oldl, oldt); + } + + public int getHorizontalScrollbarHeight() { + return WebView.this.getHorizontalScrollbarHeight(); + } + + public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, + int l, int t, int r, int b) { + WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b); + } + + // ---- Access to (non-public) fields ---- + /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */ + public void setScrollXRaw(int scrollX) { + WebView.this.mScrollX = scrollX; + } + + /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */ + public void setScrollYRaw(int scrollY) { + WebView.this.mScrollY = scrollY; + } + + } + + //------------------------------------------------------------------------- + // Package-private internal stuff + //------------------------------------------------------------------------- + + // Only used by android.webkit.FindActionModeCallback. + void setFindDialogFindListener(FindListener listener) { + checkThread(); + setupFindListenerIfNeeded(); + mFindListener.mFindDialogFindListener = listener; + } + + // Only used by android.webkit.FindActionModeCallback. + void notifyFindDialogDismissed() { + checkThread(); + mProvider.notifyFindDialogDismissed(); + } + + //------------------------------------------------------------------------- + // Private internal stuff + //------------------------------------------------------------------------- + + private WebViewProvider mProvider; + + /** + * In addition to the FindListener that the user may set via the WebView.setFindListener + * API, FindActionModeCallback will register it's own FindListener. We keep them separate + * via this class so that the two FindListeners can potentially exist at once. + */ + private class FindListenerDistributor implements FindListener { + private FindListener mFindDialogFindListener; + private FindListener mUserFindListener; + + @Override + public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, + boolean isDoneCounting) { + if (mFindDialogFindListener != null) { + mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, + isDoneCounting); + } + + if (mUserFindListener != null) { + mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches, + isDoneCounting); + } + } + } + private FindListenerDistributor mFindListener; + + private void setupFindListenerIfNeeded() { + if (mFindListener == null) { + mFindListener = new FindListenerDistributor(); + mProvider.setFindListener(mFindListener); + } + } + + private void ensureProviderCreated() { + checkThread(); + if (mProvider == null) { + // As this can get called during the base class constructor chain, pass the minimum + // number of dependencies here; the rest are deferred to init(). + mProvider = getFactory().createWebView(this, new PrivateAccess()); + } + } + + private static WebViewFactoryProvider getFactory() { + return WebViewFactory.getProvider(); + } + + private final Looper mWebViewThread = Looper.myLooper(); + + private void checkThread() { + // Ignore mWebViewThread == null because this can be called during in the super class + // constructor, before this class's own constructor has even started. + if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) { + Throwable throwable = new Throwable( + "A WebView method was called on thread '" + + Thread.currentThread().getName() + "'. " + + "All WebView methods must be called on the same thread. " + + "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() + + ", FYI main Looper is " + Looper.getMainLooper() + ")"); + Log.w(LOGTAG, Log.getStackTraceString(throwable)); + StrictMode.onWebViewMethodCalledOnWrongThread(throwable); + + if (sEnforceThreadChecking) { + throw new RuntimeException(throwable); + } + } + } + + //------------------------------------------------------------------------- + // Override View methods + //------------------------------------------------------------------------- + + // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures + // there's a corresponding override (or better, caller) for each of them in here. + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mProvider.getViewDelegate().onAttachedToWindow(); + } + + /** @hide */ + @Override + protected void onDetachedFromWindowInternal() { + mProvider.getViewDelegate().onDetachedFromWindow(); + super.onDetachedFromWindowInternal(); + } + + /** @hide */ + @Override + public void onMovedToDisplay(int displayId, Configuration config) { + mProvider.getViewDelegate().onMovedToDisplay(displayId, config); + } + + @Override + public void setLayoutParams(ViewGroup.LayoutParams params) { + mProvider.getViewDelegate().setLayoutParams(params); + } + + @Override + public void setOverScrollMode(int mode) { + super.setOverScrollMode(mode); + // This method may be called in the constructor chain, before the WebView provider is + // created. + ensureProviderCreated(); + mProvider.getViewDelegate().setOverScrollMode(mode); + } + + @Override + public void setScrollBarStyle(int style) { + mProvider.getViewDelegate().setScrollBarStyle(style); + super.setScrollBarStyle(style); + } + + @Override + protected int computeHorizontalScrollRange() { + return mProvider.getScrollDelegate().computeHorizontalScrollRange(); + } + + @Override + protected int computeHorizontalScrollOffset() { + return mProvider.getScrollDelegate().computeHorizontalScrollOffset(); + } + + @Override + protected int computeVerticalScrollRange() { + return mProvider.getScrollDelegate().computeVerticalScrollRange(); + } + + @Override + protected int computeVerticalScrollOffset() { + return mProvider.getScrollDelegate().computeVerticalScrollOffset(); + } + + @Override + protected int computeVerticalScrollExtent() { + return mProvider.getScrollDelegate().computeVerticalScrollExtent(); + } + + @Override + public void computeScroll() { + mProvider.getScrollDelegate().computeScroll(); + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + return mProvider.getViewDelegate().onHoverEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return mProvider.getViewDelegate().onTouchEvent(event); + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return mProvider.getViewDelegate().onGenericMotionEvent(event); + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + return mProvider.getViewDelegate().onTrackballEvent(event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event); + } + + /* + TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not + to be delegating them too. + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyPreIme(keyCode, event); + } + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyLongPress(keyCode, event); + } + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + return mProvider.getViewDelegate().onKeyShortcut(keyCode, event); + } + */ + + @Override + public AccessibilityNodeProvider getAccessibilityNodeProvider() { + AccessibilityNodeProvider provider = + mProvider.getViewDelegate().getAccessibilityNodeProvider(); + return provider == null ? super.getAccessibilityNodeProvider() : provider; + } + + @Deprecated + @Override + public boolean shouldDelayChildPressedState() { + return mProvider.getViewDelegate().shouldDelayChildPressedState(); + } + + @Override + public CharSequence getAccessibilityClassName() { + return WebView.class.getName(); + } + + @Override + public void onProvideVirtualStructure(ViewStructure structure) { + mProvider.getViewDelegate().onProvideVirtualStructure(structure); + } + + /** + * {@inheritDoc} + * + * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages + * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is + * understood by the {@link android.service.autofill.AutofillService} implementations: + * + * <ol> + * <li>Only the HTML nodes inside a {@code FORM} are generated. + * <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the + * node representing the WebView. + * <li>If a web page has multiple {@code FORM}s, only the data for the current form is + * represented—if the user taps a field from another form, then the current autofill + * context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and + * a new context is created for that {@code FORM}. + * <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in + * the view structure until the user taps a field from a {@code FORM} inside the + * {@code IFRAME}, in which case it would be treated the same way as multiple forms described + * above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the + * {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node. + * <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to + * {@link ViewStructure#setAutofillHints(String[])}. + * <li>If the view is editable, the {@link ViewStructure#setAutofillType(int)} and + * {@link ViewStructure#setAutofillValue(AutofillValue)} must be set. + * <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}. + * <li>Other HTML attributes can be represented through + * {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}. + * </ol> + * + * <p>If the WebView implementation can determine that the value of a field was set statically + * (for example, not through Javascript), it should also call + * {@code structure.setDataIsSensitive(false)}. + * + * <p>For example, an HTML form with 2 fields for username and password: + * + * <pre class="prettyprint"> + * <input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"> + * <input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"> + * </pre> + * + * <p>Would map to: + * + * <pre class="prettyprint"> + * int index = structure.addChildCount(2); + * ViewStructure username = structure.newChild(index); + * username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child + * username.setAutofillHints("username"); + * username.setHtmlInfo(username.newHtmlInfoBuilder("input") + * .addAttribute("type", "text") + * .addAttribute("name", "username") + * .build()); + * username.setHint("Email or username"); + * username.setAutofillType(View.AUTOFILL_TYPE_TEXT); + * username.setAutofillValue(AutofillValue.forText("Type your username")); + * // Value of the field is not sensitive because it was created statically and not changed. + * username.setDataIsSensitive(false); + * + * ViewStructure password = structure.newChild(index + 1); + * username.setAutofillId(structure, 2); // id 2 - second child + * password.setAutofillHints("current-password"); + * password.setHtmlInfo(password.newHtmlInfoBuilder("input") + * .addAttribute("type", "password") + * .addAttribute("name", "password") + * .build()); + * password.setHint("Password"); + * password.setAutofillType(View.AUTOFILL_TYPE_TEXT); + * </pre> + */ + @Override + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { + mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags); + } + + @Override + public void autofill(SparseArray<AutofillValue>values) { + mProvider.getViewDelegate().autofill(values); + } + + /** @hide */ + @Override + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); + mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info); + } + + /** @hide */ + @Override + public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { + super.onInitializeAccessibilityEventInternal(event); + mProvider.getViewDelegate().onInitializeAccessibilityEvent(event); + } + + /** @hide */ + @Override + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + return mProvider.getViewDelegate().performAccessibilityAction(action, arguments); + } + + /** @hide */ + @Override + protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, + int l, int t, int r, int b) { + mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b); + } + + @Override + protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { + mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mProvider.getViewDelegate().onWindowVisibilityChanged(visibility); + } + + @Override + protected void onDraw(Canvas canvas) { + mProvider.getViewDelegate().onDraw(canvas); + } + + @Override + public boolean performLongClick() { + return mProvider.getViewDelegate().performLongClick(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + mProvider.getViewDelegate().onConfigurationChanged(newConfig); + } + + /** + * Creates a new InputConnection for an InputMethod to interact with the WebView. + * This is similar to {@link View#onCreateInputConnection} but note that WebView + * calls InputConnection methods on a thread other than the UI thread. + * If these methods are overridden, then the overriding methods should respect + * thread restrictions when calling View methods or accessing data. + */ + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return mProvider.getViewDelegate().onCreateInputConnection(outAttrs); + } + + @Override + public boolean onDragEvent(DragEvent event) { + return mProvider.getViewDelegate().onDragEvent(event); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + // This method may be called in the constructor chain, before the WebView provider is + // created. + ensureProviderCreated(); + mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility); + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus); + super.onWindowFocusChanged(hasWindowFocus); + } + + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect); + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + /** @hide */ + @Override + protected boolean setFrame(int left, int top, int right, int bottom) { + return mProvider.getViewDelegate().setFrame(left, top, right, bottom); + } + + @Override + protected void onSizeChanged(int w, int h, int ow, int oh) { + super.onSizeChanged(w, h, ow, oh); + mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return mProvider.getViewDelegate().dispatchKeyEvent(event); + } + + @Override + public boolean requestFocus(int direction, Rect previouslyFocusedRect) { + return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { + return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate); + } + + @Override + public void setBackgroundColor(int color) { + mProvider.getViewDelegate().setBackgroundColor(color); + } + + @Override + public void setLayerType(int layerType, Paint paint) { + super.setLayerType(layerType, paint); + mProvider.getViewDelegate().setLayerType(layerType, paint); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + mProvider.getViewDelegate().preDispatchDraw(canvas); + super.dispatchDraw(canvas); + } + + @Override + public void onStartTemporaryDetach() { + super.onStartTemporaryDetach(); + mProvider.getViewDelegate().onStartTemporaryDetach(); + } + + @Override + public void onFinishTemporaryDetach() { + super.onFinishTemporaryDetach(); + mProvider.getViewDelegate().onFinishTemporaryDetach(); + } + + @Override + public Handler getHandler() { + return mProvider.getViewDelegate().getHandler(super.getHandler()); + } + + @Override + public View findFocus() { + return mProvider.getViewDelegate().findFocus(super.findFocus()); + } + + /** + * If WebView has already been loaded into the current process this method will return the + * package that was used to load it. Otherwise, the package that would be used if the WebView + * was loaded right now will be returned; this does not cause WebView to be loaded, so this + * information may become outdated at any time. + * The WebView package changes either when the current WebView package is updated, disabled, or + * uninstalled. It can also be changed through a Developer Setting. + * If the WebView package changes, any app process that has loaded WebView will be killed. The + * next time the app starts and loads WebView it will use the new WebView package instead. + * @return the current WebView package, or {@code null} if there is none. + */ + @Nullable + public static PackageInfo getCurrentWebViewPackage() { + PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo(); + if (webviewPackage != null) { + return webviewPackage; + } + + IWebViewUpdateService service = WebViewFactory.getUpdateService(); + if (service == null) { + return null; + } + try { + return service.getCurrentWebViewPackage(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}. + * + * @param requestCode The integer request code originally supplied to + * startActivityForResult(), allowing you to identify who this + * result came from. + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param data An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + * @hide + */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data); + } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + checkThread(); + encoder.addProperty("webview:contentHeight", mProvider.getContentHeight()); + encoder.addProperty("webview:contentWidth", mProvider.getContentWidth()); + encoder.addProperty("webview:scale", mProvider.getScale()); + encoder.addProperty("webview:title", mProvider.getTitle()); + encoder.addProperty("webview:url", mProvider.getUrl()); + encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl()); } } diff --git a/android/webkit/WebViewFactory.java b/android/webkit/WebViewFactory.java index 95cb4549..797bdfb7 100644 --- a/android/webkit/WebViewFactory.java +++ b/android/webkit/WebViewFactory.java @@ -25,7 +25,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; -import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; @@ -138,7 +137,7 @@ public final class WebViewFactory { } /** - * Load the native library for the given package name iff that package + * Load the native library for the given package name if that package * name is the same as the one providing the webview. */ public static int loadWebViewNativeLibraryFromPackage(String packageName, @@ -445,38 +444,17 @@ public final class WebViewFactory { } } - private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) { - if (DEBUG) Log.v(LOGTAG, "creating relro files"); - int numRelros = 0; - - // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any - // unexpected values will be handled there to ensure that we trigger notifying any process - // waiting on relro creation. - if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { - if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro"); - WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths[0]); - numRelros++; - } - - if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { - if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro"); - WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths[1]); - numRelros++; - } - return numRelros; - } - /** * @hide */ public static int onWebViewProviderChanged(PackageInfo packageInfo) { - String[] nativeLibs = null; + int startedRelroProcesses = 0; ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo); try { fixupStubApplicationInfo(packageInfo.applicationInfo, AppGlobals.getInitialApplication().getPackageManager()); - nativeLibs = WebViewLibraryLoader.updateWebViewZygoteVmSize(packageInfo); + startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo); } catch (Throwable t) { // Log and discard errors at this stage as we must not crash the system server. Log.e(LOGTAG, "error preparing webview native library", t); @@ -484,7 +462,7 @@ public final class WebViewFactory { WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo); - return prepareWebViewInSystemServer(nativeLibs); + return startedRelroProcesses; } private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate"; diff --git a/android/webkit/WebViewLibraryLoader.java b/android/webkit/WebViewLibraryLoader.java index fa1a3907..341c69fd 100644 --- a/android/webkit/WebViewLibraryLoader.java +++ b/android/webkit/WebViewLibraryLoader.java @@ -16,6 +16,8 @@ package android.webkit; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -26,6 +28,7 @@ import android.os.SystemProperties; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import dalvik.system.VMRuntime; @@ -36,7 +39,11 @@ import java.util.Arrays; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -class WebViewLibraryLoader { +/** + * @hide + */ +@VisibleForTesting +public class WebViewLibraryLoader { private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName(); private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 = @@ -94,7 +101,7 @@ class WebViewLibraryLoader { /** * Create a single relro file by invoking an isolated process that to do the actual work. */ - static void createRelroFile(final boolean is64Bit, String nativeLibraryPath) { + static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) { final String abi = is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]; @@ -112,12 +119,12 @@ class WebViewLibraryLoader { }; try { - if (nativeLibraryPath == null) { + if (nativeLib == null || nativeLib.path == null) { throw new IllegalArgumentException( "Native library paths to the WebView RelRo process must not be null!"); } int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess( - RelroFileCreator.class.getName(), new String[] { nativeLibraryPath }, + RelroFileCreator.class.getName(), new String[] { nativeLib.path }, "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler); if (pid <= 0) throw new Exception("Failed to start the relro file creator process"); } catch (Throwable t) { @@ -128,56 +135,77 @@ class WebViewLibraryLoader { } /** + * Perform preparations needed to allow loading WebView from an application. This method should + * be called whenever we change WebView provider. + * @return the number of relro processes started. + */ + static int prepareNativeLibraries(PackageInfo webviewPackageInfo) + throws WebViewFactory.MissingWebViewPackageException { + WebViewNativeLibrary nativeLib32bit = + getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */); + WebViewNativeLibrary nativeLib64bit = + getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */); + updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit); + + return createRelros(nativeLib32bit, nativeLib64bit); + } + + /** + * @return the number of relro processes started. + */ + private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit, + @Nullable WebViewNativeLibrary nativeLib64bit) { + if (DEBUG) Log.v(LOGTAG, "creating relro files"); + int numRelros = 0; + + if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { + if (nativeLib32bit == null) { + Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation."); + } else { + if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro"); + createRelroFile(false /* is64Bit */, nativeLib32bit); + numRelros++; + } + } + + if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { + if (nativeLib64bit == null) { + Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation."); + } else { + if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro"); + createRelroFile(true /* is64Bit */, nativeLib64bit); + numRelros++; + } + } + return numRelros; + } + + /** * * @return the native WebView libraries in the new WebView APK. */ - static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo) + private static void updateWebViewZygoteVmSize( + @Nullable WebViewNativeLibrary nativeLib32bit, + @Nullable WebViewNativeLibrary nativeLib64bit) throws WebViewFactory.MissingWebViewPackageException { // Find the native libraries of the new WebView package, to change the size of the // memory region in the Zygote reserved for the library. - String[] nativeLibs = getWebViewNativeLibraryPaths(packageInfo); - if (nativeLibs != null) { - long newVmSize = 0L; - - for (String path : nativeLibs) { - if (path == null || TextUtils.isEmpty(path)) continue; - if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path); - File f = new File(path); - if (f.exists()) { - newVmSize = Math.max(newVmSize, f.length()); - continue; - } - if (path.contains("!/")) { - String[] split = TextUtils.split(path, "!/"); - if (split.length == 2) { - try (ZipFile z = new ZipFile(split[0])) { - ZipEntry e = z.getEntry(split[1]); - if (e != null && e.getMethod() == ZipEntry.STORED) { - newVmSize = Math.max(newVmSize, e.getSize()); - continue; - } - } - catch (IOException e) { - Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e); - } - } - } - Log.e(LOGTAG, "error sizing load for " + path); - } + long newVmSize = 0L; - if (DEBUG) { - Log.v(LOGTAG, "Based on library size, need " + newVmSize - + " bytes of address space."); - } - // The required memory can be larger than the file on disk (due to .bss), and an - // upgraded version of the library will likely be larger, so always attempt to - // reserve twice as much as we think to allow for the library to grow during this - // boot cycle. - newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES); - Log.d(LOGTAG, "Setting new address space to " + newVmSize); - setWebViewZygoteVmSize(newVmSize); + if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size); + if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size); + + if (DEBUG) { + Log.v(LOGTAG, "Based on library size, need " + newVmSize + + " bytes of address space."); } - return nativeLibs; + // The required memory can be larger than the file on disk (due to .bss), and an + // upgraded version of the library will likely be larger, so always attempt to + // reserve twice as much as we think to allow for the library to grow during this + // boot cycle. + newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES); + Log.d(LOGTAG, "Setting new address space to " + newVmSize); + setWebViewZygoteVmSize(newVmSize); } /** @@ -227,64 +255,78 @@ class WebViewLibraryLoader { /** * Fetch WebView's native library paths from {@param packageInfo}. + * @hide */ - static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo) - throws WebViewFactory.MissingWebViewPackageException { + @Nullable + @VisibleForTesting + public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo, + boolean is64bit) throws WebViewFactory.MissingWebViewPackageException { ApplicationInfo ai = packageInfo.applicationInfo; final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai); - String path32; - String path64; - boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi); - if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) { - // Multi-arch case. - if (primaryArchIs64bit) { - // Primary arch: 64-bit, secondary: 32-bit. - path64 = ai.nativeLibraryDir; - path32 = ai.secondaryNativeLibraryDir; - } else { - // Primary arch: 32-bit, secondary: 64-bit. - path64 = ai.secondaryNativeLibraryDir; - path32 = ai.nativeLibraryDir; - } - } else if (primaryArchIs64bit) { - // Single-arch 64-bit. - path64 = ai.nativeLibraryDir; - path32 = ""; - } else { - // Single-arch 32-bit. - path32 = ai.nativeLibraryDir; - path64 = ""; + String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */); + + WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName, + is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir); + + if (DEBUG) { + Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path)); } + return lib; + } - // Form the full paths to the extracted native libraries. - // If libraries were not extracted, try load from APK paths instead. - if (!TextUtils.isEmpty(path32)) { - path32 += "/" + nativeLibFileName; - File f = new File(path32); - if (!f.exists()) { - path32 = getLoadFromApkPath(ai.sourceDir, - Build.SUPPORTED_32_BIT_ABIS, - nativeLibFileName); - } + /** + * @return the directory of the native WebView library with bitness {@param is64bit}. + * @hide + */ + @VisibleForTesting + public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) { + // Primary arch has the same bitness as the library we are looking for. + if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir; + + // Secondary arch has the same bitness as the library we are looking for. + if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) { + return ai.secondaryNativeLibraryDir; } - if (!TextUtils.isEmpty(path64)) { - path64 += "/" + nativeLibFileName; - File f = new File(path64); - if (!f.exists()) { - path64 = getLoadFromApkPath(ai.sourceDir, - Build.SUPPORTED_64_BIT_ABIS, - nativeLibFileName); - } + + return ""; + } + + /** + * @return an object describing a native WebView library given the directory path of that + * library, or null if the library couldn't be found. + */ + @Nullable + private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai, + String nativeLibFileName, String[] abiList, String libDirectory) + throws WebViewFactory.MissingWebViewPackageException { + if (TextUtils.isEmpty(libDirectory)) return null; + String libPath = libDirectory + "/" + nativeLibFileName; + File f = new File(libPath); + if (f.exists()) { + return new WebViewNativeLibrary(libPath, f.length()); + } else { + return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName); } + } + + /** + * @hide + */ + @VisibleForTesting + public static class WebViewNativeLibrary { + public final String path; + public final long size; - if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64); - return new String[] { path32, path64 }; + WebViewNativeLibrary(String path, long size) { + this.path = path; + this.size = size; + } } - private static String getLoadFromApkPath(String apkPath, - String[] abiList, - String nativeLibFileName) + private static WebViewNativeLibrary getLoadFromApkPath(String apkPath, + String[] abiList, + String nativeLibFileName) throws WebViewFactory.MissingWebViewPackageException { // Search the APK for a native library conforming to a listed ABI. try (ZipFile z = new ZipFile(apkPath)) { @@ -293,13 +335,13 @@ class WebViewLibraryLoader { ZipEntry e = z.getEntry(entry); if (e != null && e.getMethod() == ZipEntry.STORED) { // Return a path formatted for dlopen() load from APK. - return apkPath + "!/" + entry; + return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize()); } } } catch (IOException e) { throw new WebViewFactory.MissingWebViewPackageException(e); } - return ""; + return null; } /** diff --git a/android/widget/AdapterView.java b/android/widget/AdapterView.java index dd01251c..6c192563 100644 --- a/android/widget/AdapterView.java +++ b/android/widget/AdapterView.java @@ -202,7 +202,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { * The last selected position we used when notifying */ int mOldSelectedPosition = INVALID_POSITION; - + /** * The id of the last selected position we used when notifying */ @@ -382,7 +382,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { * position is different from the previously selected position or if * there was no selected item.</p> * - * Impelmenters can call getItemAtPosition(position) if they need to access the + * Implementers can call getItemAtPosition(position) if they need to access the * data associated with the selected item. * * @param parent The AdapterView where the selection happened @@ -778,8 +778,8 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { // We are now GONE, so pending layouts will not be dispatched. // Force one here to make sure that the state of the list matches // the state of the adapter. - if (mDataChanged) { - this.onLayout(false, mLeft, mTop, mRight, mBottom); + if (mDataChanged) { + this.onLayout(false, mLeft, mTop, mRight, mBottom); } } else { if (mEmptyView != null) mEmptyView.setVisibility(View.GONE); @@ -1304,4 +1304,4 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { structure.setAutofillOptions(options); } } -}
\ No newline at end of file +} diff --git a/android/widget/Editor.java b/android/widget/Editor.java index 384f4f83..e6da69dc 100644 --- a/android/widget/Editor.java +++ b/android/widget/Editor.java @@ -129,7 +129,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; - +import java.util.Map; /** * Helper class used by TextView to handle editable text views. @@ -163,9 +163,9 @@ public class Editor { private static final int MENU_ITEM_ORDER_REPLACE = 9; private static final int MENU_ITEM_ORDER_AUTOFILL = 10; private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11; + private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100; - private static final float MAGNIFIER_ZOOM = 1.25f; @IntDef({MagnifierHandleTrigger.SELECTION_START, MagnifierHandleTrigger.SELECTION_END, MagnifierHandleTrigger.INSERTION}) @@ -3793,6 +3793,7 @@ public class Editor { private final RectF mSelectionBounds = new RectF(); private final boolean mHasSelection; private final int mHandleHeight; + private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>(); public TextActionModeCallback(boolean hasSelection) { mHasSelection = hasSelection; @@ -3820,6 +3821,8 @@ public class Editor { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mAssistClickHandlers.clear(); + mode.setTitle(null); mode.setSubtitle(null); mode.setTitleOptionalHint(true); @@ -3903,14 +3906,14 @@ public class Editor { updateSelectAllItem(menu); updateReplaceItem(menu); - updateAssistMenuItem(menu); + updateAssistMenuItems(menu); } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { updateSelectAllItem(menu); updateReplaceItem(menu); - updateAssistMenuItem(menu); + updateAssistMenuItems(menu); Callback customCallback = getCustomCallback(); if (customCallback != null) { @@ -3943,32 +3946,118 @@ public class Editor { } } - private void updateAssistMenuItem(Menu menu) { - menu.removeItem(TextView.ID_ASSIST); + private void updateAssistMenuItems(Menu menu) { + clearAssistMenuItems(menu); + if (!mTextView.isDeviceProvisioned()) { + return; + } final TextClassification textClassification = getSelectionActionModeHelper().getTextClassification(); - if (canAssist()) { - menu.add(TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, - textClassification.getLabel()) - .setIcon(textClassification.getIcon()) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - mMetricsLogger.write( - new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST) - .setType(MetricsEvent.TYPE_OPEN) - .setSubtype(textClassification.getLogType())); + final int count = textClassification != null ? textClassification.getActionCount() : 0; + for (int i = 0; i < count; i++) { + if (!isValidAssistMenuItem(i)) { + continue; + } + final int groupId = TextView.ID_ASSIST; + final int order = (i == 0) + ? MENU_ITEM_ORDER_ASSIST + : MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i; + final int id = (i == 0) ? TextView.ID_ASSIST : Menu.NONE; + final int showAsFlag = (i == 0) + ? MenuItem.SHOW_AS_ACTION_ALWAYS + : MenuItem.SHOW_AS_ACTION_NEVER; + final MenuItem item = menu.add( + groupId, id, order, textClassification.getLabel(i)) + .setIcon(textClassification.getIcon(i)) + .setIntent(textClassification.getIntent(i)); + item.setShowAsAction(showAsFlag); + mAssistClickHandlers.put(item, textClassification.getOnClickListener(i)); + if (id == TextView.ID_ASSIST) { + mMetricsLogger.write( + new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST) + .setType(MetricsEvent.TYPE_OPEN) + .setSubtype(textClassification.getLogType())); + } + } + } + + private void clearAssistMenuItems(Menu menu) { + int i = 0; + while (i < menu.size()) { + final MenuItem menuItem = menu.getItem(i); + if (menuItem.getGroupId() == TextView.ID_ASSIST) { + menu.removeItem(menuItem.getItemId()); + continue; + } + i++; + } + } + + private boolean isValidAssistMenuItem(int index) { + final TextClassification textClassification = + getSelectionActionModeHelper().getTextClassification(); + if (!mTextView.isDeviceProvisioned() || textClassification == null + || index < 0 || index >= textClassification.getActionCount()) { + return false; + } + final Drawable icon = textClassification.getIcon(index); + final CharSequence label = textClassification.getLabel(index); + final boolean hasUi = icon != null || !TextUtils.isEmpty(label); + final OnClickListener onClick = textClassification.getOnClickListener(index); + final Intent intent = textClassification.getIntent(index); + final boolean hasAction = onClick != null || isSupportedIntent(intent); + return hasUi && hasAction; + } + + private boolean isSupportedIntent(Intent intent) { + if (intent == null) { + return false; + } + final Context context = mTextView.getContext(); + final ResolveInfo info = context.getPackageManager().resolveActivity(intent, 0); + final boolean samePackage = context.getPackageName().equals( + info.activityInfo.packageName); + if (samePackage) { + return true; } + + final boolean exported = info.activityInfo.exported; + final boolean requiresPermission = info.activityInfo.permission != null; + final boolean hasPermission = !requiresPermission + || context.checkSelfPermission(info.activityInfo.permission) + == PackageManager.PERMISSION_GRANTED; + return exported && hasPermission; } - private boolean canAssist() { + private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) { + Preconditions.checkArgument(assistMenuItem.getGroupId() == TextView.ID_ASSIST); + final TextClassification textClassification = getSelectionActionModeHelper().getTextClassification(); - return mTextView.isDeviceProvisioned() - && textClassification != null - && (textClassification.getIcon() != null - || !TextUtils.isEmpty(textClassification.getLabel())) - && (textClassification.getOnClickListener() != null - || (textClassification.getIntent() != null - && mTextView.getContext().canStartActivityForResult())); + if (!mTextView.isDeviceProvisioned() || textClassification == null) { + // No textClassification result to handle the click. Eat the click. + return true; + } + + OnClickListener onClickListener = mAssistClickHandlers.get(assistMenuItem); + if (onClickListener == null) { + final Intent intent = assistMenuItem.getIntent(); + if (intent != null) { + onClickListener = TextClassification.createStartActivityOnClickListener( + mTextView.getContext(), intent); + } + } + if (onClickListener != null) { + onClickListener.onClick(mTextView); + stopTextActionMode(); + if (assistMenuItem.getItemId() == TextView.ID_ASSIST) { + mMetricsLogger.action( + MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST, + textClassification.getLogType()); + } + } + // We tried our best. + return true; } @Override @@ -3982,25 +4071,7 @@ public class Editor { if (customCallback != null && customCallback.onActionItemClicked(mode, item)) { return true; } - final TextClassification textClassification = - getSelectionActionModeHelper().getTextClassification(); - if (TextView.ID_ASSIST == item.getItemId() && textClassification != null) { - final OnClickListener onClickListener = - textClassification.getOnClickListener(); - if (onClickListener != null) { - onClickListener.onClick(mTextView); - } else { - final Intent intent = textClassification.getIntent(); - if (intent != null) { - TextClassification.createStartActivityOnClickListener( - mTextView.getContext(), intent) - .onClick(mTextView); - } - } - mMetricsLogger.action( - MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST, - textClassification.getLogType()); - stopTextActionMode(); + if (item.getGroupId() == TextView.ID_ASSIST && onAssistMenuItemClicked(item)) { return true; } return mTextView.onTextContextMenuItem(item.getItemId()); @@ -4029,6 +4100,8 @@ public class Editor { if (mSelectionModifierCursorController != null) { mSelectionModifierCursorController.hide(); } + + mAssistClickHandlers.clear(); } @Override @@ -4547,7 +4620,7 @@ public class Editor { + mTextView.getTotalPaddingTop() - mTextView.getScrollY(); suspendBlink(); - mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM); + mMagnifier.show(xPosInView, yPosInView); } protected final void dismissMagnifier() { @@ -6560,6 +6633,9 @@ public class Editor { private void loadSupportedActivities() { mSupportedActivities.clear(); + if (!mContext.canStartActivityForResult()) { + return; + } PackageManager packageManager = mTextView.getContext().getPackageManager(); List<ResolveInfo> unfiltered = packageManager.queryIntentActivities(createProcessTextIntent(), 0); diff --git a/android/widget/ListPopupWindow.java b/android/widget/ListPopupWindow.java index 0d676153..adf366a4 100644 --- a/android/widget/ListPopupWindow.java +++ b/android/widget/ListPopupWindow.java @@ -657,6 +657,7 @@ public class ListPopupWindow implements ShowableListMenu { mPopup.update(getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec, (heightSpec < 0)? -1 : heightSpec); + mPopup.getContentView().restoreDefaultFocus(); } else { final int widthSpec; if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { @@ -695,6 +696,7 @@ public class ListPopupWindow implements ShowableListMenu { mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, mDropDownGravity); mDropDownList.setSelection(ListView.INVALID_POSITION); + mPopup.getContentView().restoreDefaultFocus(); if (!mModal || mDropDownList.isInTouchMode()) { clearListSelection(); diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java index 23ebadb3..8dc8cab1 100644 --- a/android/widget/PopupWindow.java +++ b/android/widget/PopupWindow.java @@ -2461,14 +2461,14 @@ public class PopupWindow { for (int i = 0; i < count; i++) { final View child = getChildAt(i); enterTransition.addTarget(child); - child.setVisibility(View.INVISIBLE); + child.setTransitionVisibility(View.INVISIBLE); } TransitionManager.beginDelayedTransition(this, enterTransition); for (int i = 0; i < count; i++) { final View child = getChildAt(i); - child.setVisibility(View.VISIBLE); + child.setTransitionVisibility(View.VISIBLE); } } diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java index 631f3882..e3309161 100644 --- a/android/widget/RemoteViews.java +++ b/android/widget/RemoteViews.java @@ -81,6 +81,7 @@ import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; import java.util.Stack; import java.util.concurrent.Executor; @@ -185,6 +186,9 @@ public class RemoteViews implements Parcelable, Filter { */ private boolean mIsWidgetCollectionChild = false; + /** Class cookies of the Parcel this instance was read from. */ + private final Map<Class, Object> mClassCookies; + private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); @@ -1505,10 +1509,10 @@ public class RemoteViews implements Parcelable, Filter { } ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, - int depth) { + int depth, Map<Class, Object> classCookies) { viewId = parcel.readInt(); mIndex = parcel.readInt(); - mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth); + mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies); } public void writeToParcel(Parcel dest, int flags) { @@ -2120,6 +2124,7 @@ public class RemoteViews implements Parcelable, Filter { mApplication = application; mLayoutId = layoutId; mBitmapCache = new BitmapCache(); + mClassCookies = null; } private boolean hasLandscapeAndPortraitLayouts() { @@ -2149,6 +2154,9 @@ public class RemoteViews implements Parcelable, Filter { mBitmapCache = new BitmapCache(); configureRemoteViewsAsChild(landscape); configureRemoteViewsAsChild(portrait); + + mClassCookies = (portrait.mClassCookies != null) + ? portrait.mClassCookies : landscape.mClassCookies; } /** @@ -2161,15 +2169,16 @@ public class RemoteViews implements Parcelable, Filter { mLayoutId = src.mLayoutId; mIsWidgetCollectionChild = src.mIsWidgetCollectionChild; mReapplyDisallowed = src.mReapplyDisallowed; + mClassCookies = src.mClassCookies; if (src.hasLandscapeAndPortraitLayouts()) { mLandscape = new RemoteViews(src.mLandscape); mPortrait = new RemoteViews(src.mPortrait); - } if (src.mActions != null) { Parcel p = Parcel.obtain(); + p.putClassCookies(mClassCookies); src.writeActionsToParcel(p); p.setDataPosition(0); // Since src is already in memory, we do not care about stack overflow as it has @@ -2189,10 +2198,11 @@ public class RemoteViews implements Parcelable, Filter { * @param parcel */ public RemoteViews(Parcel parcel) { - this(parcel, null, null, 0); + this(parcel, null, null, 0, null); } - private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth) { + private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth, + Map<Class, Object> classCookies) { if (depth > MAX_NESTED_VIEWS && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) { throw new IllegalArgumentException("Too many nested views."); @@ -2204,8 +2214,11 @@ public class RemoteViews implements Parcelable, Filter { // We only store a bitmap cache in the root of the RemoteViews. if (bitmapCache == null) { mBitmapCache = new BitmapCache(parcel); + // Store the class cookies such that they are available when we clone this RemoteView. + mClassCookies = parcel.copyClassCookies(); } else { setBitmapCache(bitmapCache); + mClassCookies = classCookies; setNotRoot(); } @@ -2218,8 +2231,9 @@ public class RemoteViews implements Parcelable, Filter { readActionsFromParcel(parcel, depth); } else { // MODE_HAS_LANDSCAPE_AND_PORTRAIT - mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth); - mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth); + mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies); + mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth, + mClassCookies); mApplication = mPortrait.mApplication; mLayoutId = mPortrait.getLayoutId(); } @@ -2246,7 +2260,8 @@ public class RemoteViews implements Parcelable, Filter { case REFLECTION_ACTION_TAG: return new ReflectionAction(parcel); case VIEW_GROUP_ACTION_ADD_TAG: - return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth); + return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth, + mClassCookies); case VIEW_GROUP_ACTION_REMOVE_TAG: return new ViewGroupActionRemove(parcel); case VIEW_CONTENT_NAVIGATION_TAG: diff --git a/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/com/android/car/setupwizardlib/CarSetupWizardLayout.java new file mode 100644 index 00000000..0f5b84b1 --- /dev/null +++ b/com/android/car/setupwizardlib/CarSetupWizardLayout.java @@ -0,0 +1,143 @@ +/* + * 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.car.setupwizardlib; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + + +/** + * Custom layout for the Car Setup Wizard. + */ +public class CarSetupWizardLayout extends LinearLayout { + private View mBackButton; + + private Button mContinueButton; + + private RelativeLayout mHeader; + + public CarSetupWizardLayout(Context context) { + this(context, null); + } + + public CarSetupWizardLayout(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public CarSetupWizardLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + /** + * On initialization, the layout gets all of the custom attributes and initializes + * the custom views that can be set by the user (e.g. back button, continue button). + */ + public CarSetupWizardLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + TypedArray attrArray = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.CarSetupWizardLayout, + 0, 0); + + init(attrArray); + + } + + /** + * Inflates the layout and sets the custom views (e.g. back button, continue button). + */ + void init(TypedArray attrArray) { + boolean showBackButton; + boolean showContinueButton; + String continueButtonText; + try { + showBackButton = attrArray.getBoolean( + R.styleable.CarSetupWizardLayout_showBackButton, true); + showContinueButton = attrArray.getBoolean( + R.styleable.CarSetupWizardLayout_showContinueButton, true); + continueButtonText = attrArray.getString( + R.styleable.CarSetupWizardLayout_continueButtonText); + } finally { + attrArray.recycle(); + } + + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.car_setup_wizard_layout, this); + + mHeader = findViewById(R.id.header); + + // Set the back button visibility based on the custom attribute. + mBackButton = findViewById(R.id.back_button); + if (!showBackButton) { + setBackButtonVisibility(View.GONE); + } + + // Set the continue button visibility and text based on the custom attributes. + mContinueButton = findViewById(R.id.continue_button); + if (showContinueButton) { + setContinueButtonText(continueButtonText); + } else { + setContinueButtonVisibility(View.GONE); + } + + // TODO: Handle loading bar logic + } + + /** + * Set the back button visibility to the given visibility. + */ + public void setBackButtonVisibility(int visibility) { + mBackButton.setVisibility(visibility); + } + + /** + * Set the continue button text to given text. + */ + public void setContinueButtonText(String text) { + mContinueButton.setText(text); + } + + /** + * Set the continue button visibility to given visibility. + */ + public void setContinueButtonVisibility(int visibility) { + mContinueButton.setVisibility(visibility); + } + + /** + * Set the back button onClickListener to given listener. Can be null if the listener should + * be overridden so no callback is made. + */ + public void setBackButtonListener(@Nullable View.OnClickListener listener) { + mBackButton.setOnClickListener(listener); + } + + /** + * Set the continue button onClickListener to then given listener. Can be null if the listener + * should be overridden so no callback is made. + */ + public void setContinueButtonListener(@Nullable View.OnClickListener listener) { + mContinueButton.setOnClickListener(listener); + } +} diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java index 60ec8a96..29433f3f 100644 --- a/com/android/commands/pm/Pm.java +++ b/com/android/commands/pm/Pm.java @@ -116,9 +116,8 @@ public final class Pm { } public int run(String[] args) throws RemoteException { - boolean validCommand = false; if (args.length < 1) { - return showUsage(); + return runShellCommand("package", mArgs); } mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE)); mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE)); @@ -134,18 +133,6 @@ public final class Pm { String op = args[0]; mNextArg = 1; - if ("list".equals(op)) { - return runList(); - } - - if ("path".equals(op)) { - return runPath(); - } - - if ("dump".equals(op)) { - return runDump(); - } - if ("install".equals(op)) { return runInstall(); } @@ -166,134 +153,7 @@ public final class Pm { return runInstallAbandon(); } - if ("set-installer".equals(op)) { - return runSetInstaller(); - } - - if ("uninstall".equals(op)) { - return runUninstall(); - } - - if ("clear".equals(op)) { - return runClear(); - } - - if ("enable".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); - } - - if ("disable".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED); - } - - if ("disable-user".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); - } - - if ("disable-until-used".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); - } - - if ("default-state".equals(op)) { - return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT); - } - - if ("hide".equals(op)) { - return runSetHiddenSetting(true); - } - - if ("unhide".equals(op)) { - return runSetHiddenSetting(false); - } - - if ("grant".equals(op)) { - return runGrantRevokePermission(true); - } - - if ("revoke".equals(op)) { - return runGrantRevokePermission(false); - } - - if ("reset-permissions".equals(op)) { - return runResetPermissions(); - } - - if ("set-permission-enforced".equals(op)) { - return runSetPermissionEnforced(); - } - - if ("set-app-link".equals(op)) { - return runSetAppLink(); - } - - if ("get-app-link".equals(op)) { - return runGetAppLink(); - } - - if ("set-install-location".equals(op)) { - return runSetInstallLocation(); - } - - if ("get-install-location".equals(op)) { - return runGetInstallLocation(); - } - - if ("trim-caches".equals(op)) { - return runTrimCaches(); - } - - if ("create-user".equals(op)) { - return runCreateUser(); - } - - if ("remove-user".equals(op)) { - return runRemoveUser(); - } - - if ("get-max-users".equals(op)) { - return runGetMaxUsers(); - } - - if ("force-dex-opt".equals(op)) { - return runForceDexOpt(); - } - - if ("move-package".equals(op)) { - return runMovePackage(); - } - - if ("move-primary-storage".equals(op)) { - return runMovePrimaryStorage(); - } - - if ("set-user-restriction".equals(op)) { - return runSetUserRestriction(); - } - - try { - if (args.length == 1) { - if (args[0].equalsIgnoreCase("-l")) { - validCommand = true; - return runShellCommand("package", new String[] { "list", "package" }); - } else if (args[0].equalsIgnoreCase("-lf")) { - validCommand = true; - return runShellCommand("package", new String[] { "list", "package", "-f" }); - } - } else if (args.length == 2) { - if (args[0].equalsIgnoreCase("-p")) { - validCommand = true; - return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM); - } - } - return 1; - } finally { - if (validCommand == false) { - if (op != null) { - System.err.println("Error: unknown command '" + op + "'"); - } - showUsage(); - } - } + return runShellCommand("package", mArgs); } static final class MyShellCallback extends ShellCallback { @@ -704,59 +564,6 @@ public final class Pm { } } - /** - * Execute the list sub-command. - * - * pm list [package | packages] - * pm list permission-groups - * pm list permissions - * pm list features - * pm list libraries - * pm list instrumentation - */ - private int runList() { - final String type = nextArg(); - if ("users".equals(type)) { - return runShellCommand("user", new String[] { "list" }); - } - return runShellCommand("package", mArgs); - } - - private int runUninstall() { - return runShellCommand("package", mArgs); - } - - private int runPath() { - int userId = UserHandle.USER_SYSTEM; - String option = nextOption(); - if (option != null && option.equals("--user")) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified"); - return 1; - } - return displayPackageFilePath(pkg, userId); - } - - private int runDump() { - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified"); - return 1; - } - ActivityManager.dumpPackageStateStatic(FileDescriptor.out, pkg); - return 0; - } - class LocalPackageInstallObserver extends PackageInstallObserver { boolean finished; int result; @@ -779,403 +586,16 @@ public final class Pm { } } - // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined} - private int runSetAppLink() { - int userId = UserHandle.USER_SYSTEM; - - String opt; - while ((opt = nextOption()) != null) { - if (opt.equals("--user")) { - userId = Integer.parseInt(nextOptionData()); - if (userId < 0) { - System.err.println("Error: user must be >= 0"); - return 1; - } - } else { - System.err.println("Error: unknown option: " + opt); - return showUsage(); - } - } - - // Package name to act on; required - final String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); - return showUsage(); - } - - // State to apply; {always|ask|never|undefined}, required - final String modeString = nextArg(); - if (modeString == null) { - System.err.println("Error: no app link state specified."); - return showUsage(); - } - - final int newMode; - switch (modeString.toLowerCase()) { - case "undefined": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; - break; - - case "always": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; - break; - - case "ask": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; - break; - - case "always-ask": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; - break; - - case "never": - newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; - break; - - default: - System.err.println("Error: unknown app link state '" + modeString + "'"); - return 1; - } - - try { - final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId); - if (info == null) { - System.err.println("Error: package " + pkg + " not found."); - return 1; - } - - if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) { - System.err.println("Error: package " + pkg + " does not handle web links."); - return 1; - } - - if (!mPm.updateIntentVerificationStatus(pkg, newMode, userId)) { - System.err.println("Error: unable to update app link status for " + pkg); - return 1; - } - } catch (Exception e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - - return 0; - } - - // pm get-app-link [--user USER_ID] PACKAGE - private int runGetAppLink() { - int userId = UserHandle.USER_SYSTEM; - - String opt; - while ((opt = nextOption()) != null) { - if (opt.equals("--user")) { - userId = Integer.parseInt(nextOptionData()); - if (userId < 0) { - System.err.println("Error: user must be >= 0"); - return 1; - } - } else { - System.err.println("Error: unknown option: " + opt); - return showUsage(); - } - } - - // Package name to act on; required - final String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); - return showUsage(); - } - - try { - final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId); - if (info == null) { - System.err.println("Error: package " + pkg + " not found."); - return 1; - } - - if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) { - System.err.println("Error: package " + pkg + " does not handle web links."); - return 1; - } - - System.out.println(linkStateToString(mPm.getIntentVerificationStatus(pkg, userId))); - } catch (Exception e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - - return 0; - } - - private String linkStateToString(int state) { - switch (state) { - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never"; - case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask"; - } - return "Unknown link state: " + state; - } - - private int runSetInstallLocation() { - int loc; - - String arg = nextArg(); - if (arg == null) { - System.err.println("Error: no install location specified."); - return 1; - } - try { - loc = Integer.parseInt(arg); - } catch (NumberFormatException e) { - System.err.println("Error: install location has to be a number."); - return 1; - } - try { - if (!mPm.setInstallLocation(loc)) { - System.err.println("Error: install location has to be a number."); - return 1; - } - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - private int runGetInstallLocation() { - try { - int loc = mPm.getInstallLocation(); - String locStr = "invalid"; - if (loc == PackageHelper.APP_INSTALL_AUTO) { - locStr = "auto"; - } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) { - locStr = "internal"; - } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) { - locStr = "external"; - } - System.out.println(loc + "[" + locStr + "]"); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - private int runSetInstaller() throws RemoteException { - final String targetPackage = nextArg(); - final String installerPackageName = nextArg(); - - if (targetPackage == null || installerPackageName == null) { - throw new IllegalArgumentException( - "must provide both target and installer package names"); - } - - mPm.setInstallerPackageName(targetPackage, installerPackageName); - System.out.println("Success"); - return 0; - } - - public int runCreateUser() { - String name; - int userId = -1; - int flags = 0; - String opt; - while ((opt = nextOption()) != null) { - if ("--profileOf".equals(opt)) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } else if ("--managed".equals(opt)) { - flags |= UserInfo.FLAG_MANAGED_PROFILE; - } else if ("--restricted".equals(opt)) { - flags |= UserInfo.FLAG_RESTRICTED; - } else if ("--ephemeral".equals(opt)) { - flags |= UserInfo.FLAG_EPHEMERAL; - } else if ("--guest".equals(opt)) { - flags |= UserInfo.FLAG_GUEST; - } else if ("--demo".equals(opt)) { - flags |= UserInfo.FLAG_DEMO; - } else { - System.err.println("Error: unknown option " + opt); - return showUsage(); - } - } - String arg = nextArg(); - if (arg == null) { - System.err.println("Error: no user name specified."); - return 1; - } - name = arg; - try { - UserInfo info; - if ((flags & UserInfo.FLAG_RESTRICTED) != 0) { - // In non-split user mode, userId can only be SYSTEM - int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; - info = mUm.createRestrictedProfile(name, parentUserId); - mAm.addSharedAccountsFromParentUser(parentUserId, userId, - (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); - } else if (userId < 0) { - info = mUm.createUser(name, flags); - } else { - info = mUm.createProfileForUser(name, flags, userId, null); - } - - if (info != null) { - System.out.println("Success: created user id " + info.id); - return 0; - } else { - System.err.println("Error: couldn't create User."); - return 1; - } - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - public int runRemoveUser() { - int userId; - String arg = nextArg(); - if (arg == null) { - System.err.println("Error: no user id specified."); - return 1; - } - try { - userId = Integer.parseInt(arg); - } catch (NumberFormatException e) { - System.err.println("Error: user id '" + arg + "' is not a number."); - return 1; - } - try { - if (mUm.removeUser(userId)) { - System.out.println("Success: removed user"); - return 0; - } else { - System.err.println("Error: couldn't remove user id " + userId); - return 1; - } - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - public int runGetMaxUsers() { - System.out.println("Maximum supported users: " + UserManager.getMaxSupportedUsers()); - return 0; - } - - public int runForceDexOpt() { - final String packageName = nextArg(); - try { - mPm.forceDexOpt(packageName); - return 0; - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - - public int runMovePackage() { - final String packageName = nextArg(); - String volumeUuid = nextArg(); - if ("internal".equals(volumeUuid)) { - volumeUuid = null; - } - - try { - final int moveId = mPm.movePackage(packageName, volumeUuid); - - int status = mPm.getMoveStatus(moveId); - while (!PackageManager.isMoveStatusFinished(status)) { - SystemClock.sleep(DateUtils.SECOND_IN_MILLIS); - status = mPm.getMoveStatus(moveId); - } - - if (status == PackageManager.MOVE_SUCCEEDED) { - System.out.println("Success"); - return 0; - } else { - System.err.println("Failure [" + status + "]"); - return 1; - } - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - - public int runMovePrimaryStorage() { - String volumeUuid = nextArg(); - if ("internal".equals(volumeUuid)) { - volumeUuid = null; - } - - try { - final int moveId = mPm.movePrimaryStorage(volumeUuid); - - int status = mPm.getMoveStatus(moveId); - while (!PackageManager.isMoveStatusFinished(status)) { - SystemClock.sleep(DateUtils.SECOND_IN_MILLIS); - status = mPm.getMoveStatus(moveId); - } - - if (status == PackageManager.MOVE_SUCCEEDED) { - System.out.println("Success"); - return 0; - } else { - System.err.println("Failure [" + status + "]"); - return 1; - } - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - - public int runSetUserRestriction() { - int userId = UserHandle.USER_SYSTEM; - String opt = nextOption(); - if (opt != null && "--user".equals(opt)) { - String arg = nextArg(); - if (arg == null || !isNumber(arg)) { - System.err.println("Error: valid userId not specified"); - return 1; - } - userId = Integer.parseInt(arg); - } - - String restriction = nextArg(); - String arg = nextArg(); - boolean value; - if ("1".equals(arg)) { - value = true; - } else if ("0".equals(arg)) { - value = false; - } else { - System.err.println("Error: valid value not specified"); - return 1; - } + private static boolean isNumber(String s) { try { - mUm.setUserRestriction(restriction, value, userId); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - return 1; + Integer.parseInt(s); + } catch (NumberFormatException nfe) { + return false; } + return true; } - static class ClearDataObserver extends IPackageDataObserver.Stub { + static class ClearCacheObserver extends IPackageDataObserver.Stub { boolean finished; boolean result; @@ -1187,242 +607,10 @@ public final class Pm { notifyAll(); } } - } - - private int runClear() { - int userId = UserHandle.USER_SYSTEM; - String option = nextOption(); - if (option != null && option.equals("--user")) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified"); - return showUsage(); - } - - ClearDataObserver obs = new ClearDataObserver(); - try { - ActivityManager.getService().clearApplicationUserData(pkg, obs, userId); - synchronized (obs) { - while (!obs.finished) { - try { - obs.wait(); - } catch (InterruptedException e) { - } - } - } - - if (obs.result) { - System.out.println("Success"); - return 0; - } else { - System.err.println("Failed"); - return 1; - } - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - private static String enabledSettingToString(int state) { - switch (state) { - case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: - return "default"; - case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: - return "enabled"; - case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: - return "disabled"; - case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: - return "disabled-user"; - case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: - return "disabled-until-used"; - } - return "unknown"; - } - - private static boolean isNumber(String s) { - try { - Integer.parseInt(s); - } catch (NumberFormatException nfe) { - return false; - } - return true; - } - - private int runSetEnabledSetting(int state) { - int userId = UserHandle.USER_SYSTEM; - String option = nextOption(); - if (option != null && option.equals("--user")) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package or component specified"); - return showUsage(); - } - ComponentName cn = ComponentName.unflattenFromString(pkg); - if (cn == null) { - try { - mPm.setApplicationEnabledSetting(pkg, state, 0, userId, - "shell:" + android.os.Process.myUid()); - System.out.println("Package " + pkg + " new state: " - + enabledSettingToString( - mPm.getApplicationEnabledSetting(pkg, userId))); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } else { - try { - mPm.setComponentEnabledSetting(cn, state, 0, userId); - System.out.println("Component " + cn.toShortString() + " new state: " - + enabledSettingToString( - mPm.getComponentEnabledSetting(cn, userId))); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - } - - private int runSetHiddenSetting(boolean state) { - int userId = UserHandle.USER_SYSTEM; - String option = nextOption(); - if (option != null && option.equals("--user")) { - String optionData = nextOptionData(); - if (optionData == null || !isNumber(optionData)) { - System.err.println("Error: no USER_ID specified"); - return showUsage(); - } else { - userId = Integer.parseInt(optionData); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package or component specified"); - return showUsage(); - } - try { - mPm.setApplicationHiddenSettingAsUser(pkg, state, userId); - System.out.println("Package " + pkg + " new hidden state: " - + mPm.getApplicationHiddenSettingAsUser(pkg, userId)); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } - } - - private int runGrantRevokePermission(boolean grant) { - int userId = UserHandle.USER_SYSTEM; - - String opt = null; - while ((opt = nextOption()) != null) { - if (opt.equals("--user")) { - userId = Integer.parseInt(nextArg()); - } - } - - String pkg = nextArg(); - if (pkg == null) { - System.err.println("Error: no package specified"); - return showUsage(); - } - String perm = nextArg(); - if (perm == null) { - System.err.println("Error: no permission specified"); - return showUsage(); - } - - try { - if (grant) { - mPm.grantRuntimePermission(pkg, perm, userId); - } else { - mPm.revokeRuntimePermission(pkg, perm, userId); - } - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } catch (IllegalArgumentException e) { - System.err.println("Bad argument: " + e.toString()); - return showUsage(); - } catch (SecurityException e) { - System.err.println("Operation not allowed: " + e.toString()); - return 1; - } - } - - private int runResetPermissions() { - try { - mPm.resetRuntimePermissions(); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } catch (IllegalArgumentException e) { - System.err.println("Bad argument: " + e.toString()); - return showUsage(); - } catch (SecurityException e) { - System.err.println("Operation not allowed: " + e.toString()); - return 1; - } - } - private int runSetPermissionEnforced() { - final String permission = nextArg(); - if (permission == null) { - System.err.println("Error: no permission specified"); - return showUsage(); - } - final String enforcedRaw = nextArg(); - if (enforcedRaw == null) { - System.err.println("Error: no enforcement specified"); - return showUsage(); - } - final boolean enforced = Boolean.parseBoolean(enforcedRaw); - try { - mPm.setPermissionEnforced(permission, enforced); - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } catch (IllegalArgumentException e) { - System.err.println("Bad argument: " + e.toString()); - return showUsage(); - } catch (SecurityException e) { - System.err.println("Operation not allowed: " + e.toString()); - return 1; - } } - static class ClearCacheObserver extends IPackageDataObserver.Stub { + static class ClearDataObserver extends IPackageDataObserver.Stub { boolean finished; boolean result; @@ -1434,66 +622,6 @@ public final class Pm { notifyAll(); } } - - } - - private int runTrimCaches() { - String size = nextArg(); - if (size == null) { - System.err.println("Error: no size specified"); - return showUsage(); - } - long multiplier = 1; - int len = size.length(); - char c = size.charAt(len - 1); - if (c < '0' || c > '9') { - if (c == 'K' || c == 'k') { - multiplier = 1024L; - } else if (c == 'M' || c == 'm') { - multiplier = 1024L*1024L; - } else if (c == 'G' || c == 'g') { - multiplier = 1024L*1024L*1024L; - } else { - System.err.println("Invalid suffix: " + c); - return showUsage(); - } - size = size.substring(0, len-1); - } - long sizeVal; - try { - sizeVal = Long.parseLong(size) * multiplier; - } catch (NumberFormatException e) { - System.err.println("Error: expected number at: " + size); - return showUsage(); - } - String volumeUuid = nextArg(); - if ("internal".equals(volumeUuid)) { - volumeUuid = null; - } - ClearDataObserver obs = new ClearDataObserver(); - try { - mPm.freeStorageAndNotify(volumeUuid, sizeVal, - StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs); - synchronized (obs) { - while (!obs.finished) { - try { - obs.wait(); - } catch (InterruptedException e) { - } - } - } - return 0; - } catch (RemoteException e) { - System.err.println(e.toString()); - System.err.println(PM_NOT_RUNNING_ERR); - return 1; - } catch (IllegalArgumentException e) { - System.err.println("Bad argument: " + e.toString()); - return showUsage(); - } catch (SecurityException e) { - System.err.println("Operation not allowed: " + e.toString()); - return 1; - } } /** diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java index ae621979..6d4d4d20 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 a5c4a438..7b53918f 100644 --- a/com/android/ex/photo/PhotoViewActivity.java +++ b/com/android/ex/photo/PhotoViewActivity.java @@ -21,14 +21,14 @@ import android.content.Context; 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 @@ public class PhotoViewActivity extends AppCompatActivity mController.onCreate(savedInstanceState); } - protected PhotoViewController createController() { + public PhotoViewController createController() { return new PhotoViewController(this); } @@ -122,7 +122,7 @@ public class PhotoViewActivity extends AppCompatActivity @Override public ActionBarInterface getActionBarInterface() { if (mActionBar == null) { - mActionBar = new ActionBarWrapper(getSupportActionBar()); + mActionBar = new ActionBarWrapper(getActionBar()); } return mActionBar; } diff --git a/com/android/internal/app/NightDisplayController.java b/com/android/internal/app/NightDisplayController.java index 7a1383c7..b2053c00 100644 --- a/com/android/internal/app/NightDisplayController.java +++ b/com/android/internal/app/NightDisplayController.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.provider.Settings.Secure; +import android.provider.Settings.System; import android.util.Slog; import com.android.internal.R; @@ -76,6 +77,29 @@ public final class NightDisplayController { */ public static final int AUTO_MODE_TWILIGHT = 2; + @Retention(RetentionPolicy.SOURCE) + @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED }) + public @interface ColorMode {} + + /** + * Color mode with natural colors. + * + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_NATURAL = 0; + /** + * Color mode with boosted colors. + * + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_BOOSTED = 1; + /** + * Color mode with saturated colors. + * + * @see #setColorMode(int) + */ + public static final int COLOR_MODE_SATURATED = 2; + private final Context mContext; private final int mUserId; @@ -306,6 +330,31 @@ public final class NightDisplayController { } /** + * Get the current color mode. + */ + public int getColorMode() { + final int colorMode = System.getIntForUser(mContext.getContentResolver(), + System.DISPLAY_COLOR_MODE, COLOR_MODE_BOOSTED, mUserId); + if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) { + return COLOR_MODE_BOOSTED; + } + return colorMode; + } + + /** + * Set the current color mode. + * + * @param colorMode the color mode + */ + public void setColorMode(@ColorMode int colorMode) { + if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) { + throw new IllegalArgumentException("Invalid colorMode: " + colorMode); + } + System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode, + mUserId); + } + + /** * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated. */ public int getMinimumColorTemperature() { @@ -351,6 +400,9 @@ public final class NightDisplayController { case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE: mCallback.onColorTemperatureChanged(getColorTemperature()); break; + case System.DISPLAY_COLOR_MODE: + mCallback.onDisplayColorModeChanged(getColorMode()); + break; } } } @@ -379,6 +431,8 @@ public final class NightDisplayController { false /* notifyForDescendants */, mContentObserver, mUserId); cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE), false /* notifyForDescendants */, mContentObserver, mUserId); + cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE), + false /* notifyForDecendants */, mContentObserver, mUserId); } } } @@ -425,5 +479,12 @@ public final class NightDisplayController { * @param colorTemperature the color temperature to tint the screen */ default void onColorTemperatureChanged(int colorTemperature) {} + + /** + * Callback invoked when the color mode changes. + * + * @param displayColorMode the color mode + */ + default void onDisplayColorModeChanged(int displayColorMode) {} } } diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java index 9cc1959f..f0d05da2 100644 --- a/com/android/internal/os/BatteryStatsImpl.java +++ b/com/android/internal/os/BatteryStatsImpl.java @@ -4055,6 +4055,7 @@ public class BatteryStatsImpl extends BatteryStats { long deltaUptime = uptimeMs - mLastWakeupUptimeMs; SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason); timer.add(deltaUptime * 1000, 1); // time in in microseconds + StatsLog.write(StatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason, deltaUptime * 1000); mLastWakeupReason = null; } } @@ -4633,6 +4634,7 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: " + Integer.toHexString(mHistoryCur.states)); newHistory = true; + StatsLog.write(StatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin); } else { stopAllPhoneSignalStrengthTimersLocked(-1); } @@ -5188,6 +5190,7 @@ public class BatteryStatsImpl extends BatteryStats { if (strengthBin >= 0) { if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) { mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime); + StatsLog.write(StatsLog.WIFI_SIGNAL_STRENGTH_CHANGED, strengthBin); } mHistoryCur.states2 = (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK) @@ -6053,6 +6056,8 @@ public class BatteryStatsImpl extends BatteryStats { mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase); } mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 1); } } @@ -6061,6 +6066,10 @@ public class BatteryStatsImpl extends BatteryStats { if (mFullWifiLockOut) { mFullWifiLockOut = false; mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mFullWifiLockTimer.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 0); + } } } @@ -6074,6 +6083,8 @@ public class BatteryStatsImpl extends BatteryStats { mOnBatteryBackgroundTimeBase); } mWifiScanTimer.startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 1); } } @@ -6082,6 +6093,10 @@ public class BatteryStatsImpl extends BatteryStats { if (mWifiScanStarted) { mWifiScanStarted = false; mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs); + if (!mWifiScanTimer.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 0); + } } } @@ -8876,6 +8891,8 @@ public class BatteryStatsImpl extends BatteryStats { Wakelock wl = mWakelockStats.startObject(name); if (wl != null) { getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs); + // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere) + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1); } if (type == WAKE_TYPE_PARTIAL) { createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs); @@ -8893,7 +8910,12 @@ public class BatteryStatsImpl extends BatteryStats { public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) { Wakelock wl = mWakelockStats.stopObject(name); if (wl != null) { - getWakelockTimerLocked(wl, type).stopRunningLocked(elapsedRealtimeMs); + StopwatchTimer wlt = getWakelockTimerLocked(wl, type); + wlt.stopRunningLocked(elapsedRealtimeMs); + if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped + // TODO(statsd): Possibly use a worksource instead of a uid. + StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0); + } } if (type == WAKE_TYPE_PARTIAL) { if (mAggregatedPartialWakelockTimer != null) { @@ -11156,11 +11178,15 @@ public class BatteryStatsImpl extends BatteryStats { // This should probably be exposed in the API, though it's not critical public static final int BATTERY_PLUGGED_NONE = 0; - public void setBatteryStateLocked(int status, int health, int plugType, int level, - int temp, int volt, int chargeUAh, int chargeFullUAh) { + public void setBatteryStateLocked(final int status, final int health, final int plugType, + final int level, /* not final */ int temp, final int volt, final int chargeUAh, + final int chargeFullUAh) { // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0. temp = Math.max(0, temp); + reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null, + status, plugType, level, temp); + final boolean onBattery = plugType == BATTERY_PLUGGED_NONE; final long uptime = mClocks.uptimeMillis(); final long elapsedRealtime = mClocks.elapsedRealtime(); @@ -11337,6 +11363,24 @@ public class BatteryStatsImpl extends BatteryStats { mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh); } + // Inform StatsLog of setBatteryState changes. + // If this is the first reporting, pass in recentPast == null. + private void reportChangesToStatsLog(HistoryItem recentPast, + final int status, final int plugType, final int level, final int temp) { + + if (recentPast == null || recentPast.batteryStatus != status) { + StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, status); + } + if (recentPast == null || recentPast.batteryPlugType != plugType) { + StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, plugType); + } + if (recentPast == null || recentPast.batteryLevel != level) { + StatsLog.write(StatsLog.BATTERY_LEVEL_CHANGED, level); + } + // Let's just always print the temperature, regardless of whether it changed. + StatsLog.write(StatsLog.DEVICE_TEMPERATURE_REPORTED, temp); + } + public long getAwakeTimeBattery() { return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT); } diff --git a/com/android/internal/os/BinderInternal.java b/com/android/internal/os/BinderInternal.java index ea4575ab..5bddd2f9 100644 --- a/com/android/internal/os/BinderInternal.java +++ b/com/android/internal/os/BinderInternal.java @@ -16,9 +16,15 @@ package com.android.internal.os; +import android.annotation.NonNull; +import android.os.Handler; import android.os.IBinder; import android.os.SystemClock; import android.util.EventLog; +import android.util.Log; +import android.util.SparseIntArray; + +import com.android.internal.util.Preconditions; import dalvik.system.VMRuntime; @@ -31,11 +37,14 @@ import java.util.ArrayList; * @see IBinder */ public class BinderInternal { + private static final String TAG = "BinderInternal"; static WeakReference<GcWatcher> sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher()); static ArrayList<Runnable> sGcWatchers = new ArrayList<>(); static Runnable[] sTmpWatchers = new Runnable[1]; static long sLastGcTime; + static final BinderProxyLimitListenerDelegate sBinderProxyLimitListenerDelegate = + new BinderProxyLimitListenerDelegate(); static final class GcWatcher { @Override @@ -106,4 +115,96 @@ public class BinderInternal { static void forceBinderGc() { forceGc("Binder"); } + + /** + * Enable/disable Binder Proxy Instance Counting by Uid. While enabled, the set callback will + * be called if this process holds too many Binder Proxies on behalf of a Uid. + * @param enabled true to enable counting, false to disable + */ + public static final native void nSetBinderProxyCountEnabled(boolean enabled); + + /** + * Get the current number of Binder Proxies held for each uid. + * @return SparseIntArray mapping uids to the number of Binder Proxies currently held + */ + public static final native SparseIntArray nGetBinderProxyPerUidCounts(); + + /** + * Get the current number of Binder Proxies held for an individual uid. + * @param uid Requested uid for Binder Proxy count + * @return int with the number of Binder proxies held for a uid + */ + public static final native int nGetBinderProxyCount(int uid); + + /** + * Set the Binder Proxy watermarks. Default high watermark = 2500. Default low watermark = 2000 + * @param high The limit at which the BinderProxyListener callback will be called. + * @param low The threshold a binder count must drop below before the callback + * can be called again. (This is to avoid many repeated calls to the + * callback in a brief period of time) + */ + public static final native void nSetBinderProxyCountWatermarks(int high, int low); + + /** + * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will + * be called with the uid of the app causing too many Binder Proxies + */ + public interface BinderProxyLimitListener { + public void onLimitReached(int uid); + } + + /** + * Callback used by native code to trigger a callback in java code. The callback will be + * triggered when too many binder proxies from a uid hits the allowed limit. + * @param uid The uid of the bad behaving app sending too many binders + */ + public static void binderProxyLimitCallbackFromNative(int uid) { + sBinderProxyLimitListenerDelegate.notifyClient(uid); + } + + /** + * Set a callback to be triggered when a uid's Binder Proxy limit is reached for this process. + * @param listener OnLimitReached of listener will be called in the thread provided by handler + * @param handler must not be null, callback will be posted through the handler; + * + */ + public static void setBinderProxyCountCallback(BinderProxyLimitListener listener, + @NonNull Handler handler) { + Preconditions.checkNotNull(handler, + "Must provide NonNull Handler to setBinderProxyCountCallback when setting " + + "BinderProxyLimitListener"); + sBinderProxyLimitListenerDelegate.setListener(listener, handler); + } + + /** + * Clear the Binder Proxy callback + */ + public static void clearBinderProxyCountCallback() { + sBinderProxyLimitListenerDelegate.setListener(null, null); + } + + static private class BinderProxyLimitListenerDelegate { + private BinderProxyLimitListener mBinderProxyLimitListener; + private Handler mHandler; + + void setListener(BinderProxyLimitListener listener, Handler handler) { + synchronized (this) { + mBinderProxyLimitListener = listener; + mHandler = handler; + } + } + + void notifyClient(final int uid) { + synchronized (this) { + if (mBinderProxyLimitListener != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + mBinderProxyLimitListener.onLimitReached(uid); + } + }); + } + } + } + } } diff --git a/com/android/internal/telephony/GsmCdmaConnection.java b/com/android/internal/telephony/GsmCdmaConnection.java index 79373ae2..afef78f4 100644 --- a/com/android/internal/telephony/GsmCdmaConnection.java +++ b/com/android/internal/telephony/GsmCdmaConnection.java @@ -501,6 +501,9 @@ public class GsmCdmaConnection extends Connection { case CallFailCause.CDMA_ACCESS_BLOCKED: return DisconnectCause.CDMA_ACCESS_BLOCKED; + case CallFailCause.NORMAL_UNSPECIFIED: + return DisconnectCause.NORMAL_UNSPECIFIED; + case CallFailCause.ERROR_UNSPECIFIED: case CallFailCause.NORMAL_CLEARING: default: diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java index 16f816f4..cf520973 100644 --- a/com/android/internal/telephony/Phone.java +++ b/com/android/internal/telephony/Phone.java @@ -56,7 +56,6 @@ import android.telephony.SignalStrength; import android.telephony.SubscriptionManager; import android.telephony.VoLteServiceState; import android.text.TextUtils; -import android.util.Log; import com.android.ims.ImsCall; import com.android.ims.ImsConfig; @@ -277,8 +276,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { public SmsUsageMonitor mSmsUsageMonitor; protected AtomicReference<UiccCardApplication> mUiccApplication = new AtomicReference<UiccCardApplication>(); - - private TelephonyTester mTelephonyTester; + TelephonyTester mTelephonyTester; private String mName; private final String mActionDetached; private final String mActionAttached; diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java index 3aafdfb8..e0f94de5 100644 --- a/com/android/internal/telephony/ServiceStateTracker.java +++ b/com/android/internal/telephony/ServiceStateTracker.java @@ -2678,6 +2678,10 @@ public class ServiceStateTracker extends Handler { useDataRegStateForDataOnlyDevices(); resetServiceStateInIwlanMode(); + if (Build.IS_DEBUGGABLE && mPhone.mTelephonyTester != null) { + mPhone.mTelephonyTester.overrideServiceState(mNewSS); + } + if (DBG) { log("Poll ServiceState done: " + " oldSS=[" + mSS + "] newSS=[" + mNewSS + "]" diff --git a/com/android/internal/telephony/TelephonyTester.java b/com/android/internal/telephony/TelephonyTester.java index 0e809372..b59fcb6c 100644 --- a/com/android/internal/telephony/TelephonyTester.java +++ b/com/android/internal/telephony/TelephonyTester.java @@ -50,6 +50,10 @@ import java.util.List; * adb shell am broadcast -a com.android.internal.telephony.{name}.action_attached * adb shell am broadcast -a com.android.internal.telephony.TestConferenceEventPackage -e filename * test_filename.xml + * adb shell am broadcast -a com.android.internal.telephony.TestServiceState --ei data_rat 10 --ei + * data_roaming_type 3 + * adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset + * */ public class TelephonyTester { private static final String LOG_TAG = "TelephonyTester"; @@ -99,8 +103,24 @@ public class TelephonyTester { private static final String EXTRA_CODE = "code"; + + private static final String ACTION_TEST_SERVICE_STATE = + "com.android.internal.telephony.TestServiceState"; + + private static final String EXTRA_ACTION = "action"; + private static final String EXTRA_VOICE_RAT = "voice_rat"; + private static final String EXTRA_DATA_RAT = "data_rat"; + private static final String EXTRA_VOICE_REG_STATE = "voice_reg_state"; + private static final String EXTRA_DATA_REG_STATE = "data_reg_state"; + private static final String EXTRA_VOICE_ROAMING_TYPE = "voice_roaming_type"; + private static final String EXTRA_DATA_ROAMING_TYPE = "data_roaming_type"; + + private static final String ACTION_RESET = "reset"; + private static List<ImsExternalCallState> mImsExternalCallStates = null; + private Intent mServiceStateTestIntent; + private Phone mPhone; // The static intent receiver one for all instances and we assume this @@ -133,6 +153,13 @@ public class TelephonyTester { } else if (action.equals(ACTION_TEST_SUPP_SRVC_NOTIFICATION)) { log("handle supp service notification test intent"); sendTestSuppServiceNotification(intent); + } else if (action.equals(ACTION_TEST_SERVICE_STATE)) { + log("handle test service state changed intent"); + // Trigger the service state update. The replacement will be done in + // overrideServiceState(). + mServiceStateTestIntent = intent; + mPhone.getServiceStateTracker().sendEmptyMessage( + ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED); } else { if (DBG) log("onReceive: unknown action=" + action); } @@ -162,6 +189,9 @@ public class TelephonyTester { filter.addAction(ACTION_TEST_HANDOVER_FAIL); filter.addAction(ACTION_TEST_SUPP_SRVC_NOTIFICATION); mImsExternalCallStates = new ArrayList<ImsExternalCallState>(); + } else { + filter.addAction(ACTION_TEST_SERVICE_STATE); + log("register for intent action=" + ACTION_TEST_SERVICE_STATE); } phone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone.getHandler()); @@ -298,4 +328,43 @@ public class TelephonyTester { imsPhone.notifySuppSvcNotification(suppServiceNotification); } } -} + + void overrideServiceState(ServiceState ss) { + if (mServiceStateTestIntent == null || ss == null) return; + if (mServiceStateTestIntent.hasExtra(EXTRA_ACTION) + && ACTION_RESET.equals(mServiceStateTestIntent.getStringExtra(EXTRA_ACTION))) { + log("Service state override reset"); + return; + } + if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_REG_STATE)) { + ss.setVoiceRegState(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_REG_STATE, + ServiceState.RIL_REG_STATE_UNKNOWN)); + log("Override voice reg state with " + ss.getVoiceRegState()); + } + if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_REG_STATE)) { + ss.setDataRegState(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE, + ServiceState.RIL_REG_STATE_UNKNOWN)); + log("Override data reg state with " + ss.getDataRegState()); + } + if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_RAT)) { + ss.setRilVoiceRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_RAT, + ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)); + log("Override voice rat with " + ss.getRilVoiceRadioTechnology()); + } + if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_RAT)) { + ss.setRilDataRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_RAT, + ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)); + log("Override data rat with " + ss.getRilDataRadioTechnology()); + } + if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_ROAMING_TYPE)) { + ss.setVoiceRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_ROAMING_TYPE, + ServiceState.ROAMING_TYPE_UNKNOWN)); + log("Override voice roaming type with " + ss.getVoiceRoamingType()); + } + if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_ROAMING_TYPE)) { + ss.setDataRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_ROAMING_TYPE, + ServiceState.ROAMING_TYPE_UNKNOWN)); + log("Override data roaming type with " + ss.getDataRoamingType()); + } + } +}
\ No newline at end of file diff --git a/com/android/internal/telephony/dataconnection/DataConnection.java b/com/android/internal/telephony/dataconnection/DataConnection.java index 33d0b1d0..14b2c625 100644 --- a/com/android/internal/telephony/dataconnection/DataConnection.java +++ b/com/android/internal/telephony/dataconnection/DataConnection.java @@ -35,6 +35,7 @@ import android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.LocalLog; import android.util.Pair; import android.util.TimeUtils; @@ -50,6 +51,7 @@ import com.android.internal.telephony.RetryManager; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.AsyncChannel; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -168,6 +170,7 @@ public class DataConnection extends StateMachine { private int mDataRegState = Integer.MAX_VALUE; private NetworkInfo mNetworkInfo; private NetworkAgent mNetworkAgent; + private LocalLog mLocalLog = new LocalLog(50); int mTag; public int mCid; @@ -1835,9 +1838,14 @@ public class DataConnection extends StateMachine { private class DcNetworkAgent extends NetworkAgent { + + private NetworkCapabilities mNetworkCapabilities; + public DcNetworkAgent(Looper l, Context c, String TAG, NetworkInfo ni, NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) { super(l, c, TAG, ni, nc, lp, score, misc); + mLocalLog.log("New network agent created. capabilities=" + nc); + mNetworkCapabilities = nc; } @Override @@ -1880,6 +1888,21 @@ public class DataConnection extends StateMachine { msg.sendToTarget(); } } + + @Override + public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) { + if (!networkCapabilities.equals(mNetworkCapabilities)) { + String logStr = "Changed from " + mNetworkCapabilities + " to " + + networkCapabilities + ", Data RAT=" + + mPhone.getServiceState().getRilDataRadioTechnology() + + ", DUN APN=\"" + mDct.fetchDunApn() + "\"" + + ", mApnSetting=" + mApnSetting; + mLocalLog.log(logStr); + log(logStr); + mNetworkCapabilities = networkCapabilities; + } + super.sendNetworkCapabilities(networkCapabilities); + } } // ******* "public" interface @@ -2088,34 +2111,39 @@ public class DataConnection extends StateMachine { * @param args */ @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); pw.print("DataConnection "); super.dump(fd, pw, args); - pw.println(" mApnContexts.size=" + mApnContexts.size()); - pw.println(" mApnContexts=" + mApnContexts); - pw.flush(); - pw.println(" mDataConnectionTracker=" + mDct); - pw.println(" mApnSetting=" + mApnSetting); - pw.println(" mTag=" + mTag); - pw.println(" mCid=" + mCid); - pw.println(" mConnectionParams=" + mConnectionParams); - pw.println(" mDisconnectParams=" + mDisconnectParams); - pw.println(" mDcFailCause=" + mDcFailCause); - pw.flush(); - pw.println(" mPhone=" + mPhone); - pw.flush(); - pw.println(" mLinkProperties=" + mLinkProperties); pw.flush(); - pw.println(" mDataRegState=" + mDataRegState); - pw.println(" mRilRat=" + mRilRat); - pw.println(" mNetworkCapabilities=" + getNetworkCapabilities()); - pw.println(" mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime)); - pw.println(" mLastFailTime=" + TimeUtils.logTimeOfDay(mLastFailTime)); - pw.println(" mLastFailCause=" + mLastFailCause); + pw.increaseIndent(); + pw.println("mApnContexts.size=" + mApnContexts.size()); + pw.println("mApnContexts=" + mApnContexts); + pw.println("mDataConnectionTracker=" + mDct); + pw.println("mApnSetting=" + mApnSetting); + pw.println("mTag=" + mTag); + pw.println("mCid=" + mCid); + pw.println("mConnectionParams=" + mConnectionParams); + pw.println("mDisconnectParams=" + mDisconnectParams); + pw.println("mDcFailCause=" + mDcFailCause); + pw.println("mPhone=" + mPhone); + pw.println("mLinkProperties=" + mLinkProperties); pw.flush(); - pw.println(" mUserData=" + mUserData); - pw.println(" mInstanceNumber=" + mInstanceNumber); - pw.println(" mAc=" + mAc); + pw.println("mDataRegState=" + mDataRegState); + pw.println("mRilRat=" + mRilRat); + pw.println("mNetworkCapabilities=" + getNetworkCapabilities()); + pw.println("mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime)); + pw.println("mLastFailTime=" + TimeUtils.logTimeOfDay(mLastFailTime)); + pw.println("mLastFailCause=" + mLastFailCause); + pw.println("mUserData=" + mUserData); + pw.println("mInstanceNumber=" + mInstanceNumber); + pw.println("mAc=" + mAc); + pw.println("Network capabilities changed history:"); + pw.increaseIndent(); + mLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); + pw.decreaseIndent(); + pw.println(); pw.flush(); } } diff --git a/com/android/internal/telephony/gsm/GsmMmiCode.java b/com/android/internal/telephony/gsm/GsmMmiCode.java index 54c4d291..b9a07f9f 100644 --- a/com/android/internal/telephony/gsm/GsmMmiCode.java +++ b/com/android/internal/telephony/gsm/GsmMmiCode.java @@ -32,7 +32,9 @@ import android.content.res.Resources; import android.os.AsyncResult; import android.os.Handler; import android.os.Message; +import android.os.PersistableBundle; import android.os.ResultReceiver; +import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; import android.telephony.Rlog; import android.text.BidiFormatter; @@ -51,6 +53,7 @@ import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; import com.android.internal.telephony.uicc.IccRecords; import com.android.internal.telephony.uicc.UiccCardApplication; +import com.android.internal.util.ArrayUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -234,16 +237,20 @@ public final class GsmMmiCode extends Handler implements MmiCode { ret.mPwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM)); ret.mDialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER)); ret.mCallbackReceiver = wrappedCallback; - // According to TS 22.030 6.5.2 "Structure of the MMI", - // the dialing number should not ending with #. - // The dialing number ending # is treated as unique USSD, - // eg, *400#16 digit number# to recharge the prepaid card - // in India operator(Mumbai MTNL) + if(ret.mDialingNumber != null && ret.mDialingNumber.endsWith("#") && dialString.endsWith("#")){ + // According to TS 22.030 6.5.2 "Structure of the MMI", + // the dialing number should not ending with #. + // The dialing number ending # is treated as unique USSD, + // eg, *400#16 digit number# to recharge the prepaid card + // in India operator(Mumbai MTNL) ret = new GsmMmiCode(phone, app); ret.mPoundString = dialString; + } else if (ret.isFacToDial()) { + // This is a FAC (feature access code) to dial as a normal call. + ret = null; } } else if (dialString.endsWith("#")) { // TS 22.030 sec 6.5.3.2 @@ -795,6 +802,30 @@ public final class GsmMmiCode extends Handler implements MmiCode { return CommandsInterface.CLIR_DEFAULT; } + /** + * Returns true if the Service Code is FAC to dial as a normal call. + * + * FAC stands for feature access code and it is special patterns of characters + * to invoke certain features. + */ + private boolean isFacToDial() { + CarrierConfigManager configManager = (CarrierConfigManager) + mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); + PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId()); + if (b != null) { + String[] dialFacList = b.getStringArray(CarrierConfigManager + .KEY_FEATURE_ACCESS_CODES_STRING_ARRAY); + if (!ArrayUtils.isEmpty(dialFacList)) { + for (String fac : dialFacList) { + if (fac.equals(mSc)) { + return true; + } + } + } + } + return false; + } + boolean isActivate() { return mAction != null && mAction.equals(ACTION_ACTIVATE); } diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java index 51849ffc..bb2c34f8 100644 --- a/com/android/internal/telephony/imsphone/ImsPhone.java +++ b/com/android/internal/telephony/imsphone/ImsPhone.java @@ -1469,6 +1469,7 @@ public class ImsPhone extends ImsPhoneBase { // send an Intent sendEmergencyCallbackModeChange(); + ((GsmCdmaPhone) mDefaultPhone).notifyEmergencyCallRegistrants(false); } /** diff --git a/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/com/android/internal/telephony/imsphone/ImsPhoneBase.java index 87b96d8b..c165b03e 100644 --- a/com/android/internal/telephony/imsphone/ImsPhoneBase.java +++ b/com/android/internal/telephony/imsphone/ImsPhoneBase.java @@ -496,9 +496,6 @@ abstract class ImsPhoneBase extends Phone { return false; } - public void saveClirSetting(int commandInterfaceCLIRMode) { - } - @Override public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){ return null; diff --git a/com/android/internal/telephony/uicc/PlmnActRecord.java b/com/android/internal/telephony/uicc/PlmnActRecord.java index 2218280c..ea87c8dc 100644 --- a/com/android/internal/telephony/uicc/PlmnActRecord.java +++ b/com/android/internal/telephony/uicc/PlmnActRecord.java @@ -64,7 +64,8 @@ public class PlmnActRecord implements Parcelable { public PlmnActRecord(byte[] bytes, int offset) { if (VDBG) Rlog.v(LOG_TAG, "Creating PlmnActRecord " + offset); this.plmn = IccUtils.bcdPlmnToString(bytes, offset); - this.accessTechs = ((int) bytes[offset + 3] << 8) | bytes[offset + 4]; + this.accessTechs = (Byte.toUnsignedInt(bytes[offset + 3]) << 8) + | Byte.toUnsignedInt(bytes[offset + 4]); } private PlmnActRecord(String plmn, int accessTechs) { diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java index dad1ee2b..8aed9315 100644 --- a/com/android/internal/telephony/uicc/SIMRecords.java +++ b/com/android/internal/telephony/uicc/SIMRecords.java @@ -28,11 +28,13 @@ import android.telephony.PhoneNumberUtils; import android.telephony.Rlog; import android.telephony.SmsMessage; import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.text.TextUtils; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.MccTable; import com.android.internal.telephony.SmsConstants; +import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.gsm.SimTlv; import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType; @@ -1645,6 +1647,33 @@ public class SIMRecords extends IccRecords { } 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()); + 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"); + } } private void setSpnFromConfig(String carrier) { @@ -2072,10 +2101,14 @@ public class SIMRecords extends IccRecords { return null; } int numPlmns = data.length / packedBcdPlmnLenBytes; - String[] ret = new String[numPlmns]; + int numValidPlmns = 0; + String[] parsed = new String[numPlmns]; for (int i = 0; i < numPlmns; i++) { - ret[i] = IccUtils.bcdPlmnToString(data, i * packedBcdPlmnLenBytes); + parsed[numValidPlmns] = IccUtils.bcdPlmnToString(data, i * packedBcdPlmnLenBytes); + // we count the valid (non empty) records and only increment if valid + if (!TextUtils.isEmpty(parsed[numValidPlmns])) numValidPlmns++; } + String[] ret = Arrays.copyOf(parsed, numValidPlmns); if (VDBG) logv(description + " PLMNs: " + Arrays.toString(ret)); return ret; } diff --git a/com/android/internal/util/LocalLog.java b/com/android/internal/util/LocalLog.java index f0e61715..8edb739f 100644 --- a/com/android/internal/util/LocalLog.java +++ b/com/android/internal/util/LocalLog.java @@ -20,6 +20,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import android.util.Slog; +import android.util.proto.ProtoOutputStream; /** * Helper class for logging serious issues, which also keeps a small @@ -63,4 +64,16 @@ public class LocalLog { return true; } } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + synchronized (mLines) { + for (int i = 0; i < mLines.size(); ++i) { + proto.write(LocalLogProto.LINES, mLines.get(i)); + } + } + + proto.end(token); + } } diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java index 6d54d7b8..f6741c3b 100644 --- a/com/android/internal/widget/Magnifier.java +++ b/com/android/internal/widget/Magnifier.java @@ -63,7 +63,7 @@ public final class Magnifier { // the copy is finished. private final Handler mPixelCopyHandler = Handler.getMain(); // Current magnification scale. - private float mScale; + private final float mZoomScale; // Timer used to schedule the copy task. private Timer mTimer; @@ -76,11 +76,12 @@ public final class Magnifier { 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); - final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation); + mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale); mWindow = new PopupWindow(context); mWindow.setContentView(content); @@ -90,7 +91,9 @@ public final class Magnifier { mWindow.setTouchable(false); mWindow.setBackgroundDrawable(null); - mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888); + final int bitmapWidth = (int) (mWindowWidth / mZoomScale); + final int bitmapHeight = (int) (mWindowHeight / mZoomScale); + mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); getImageView().setImageBitmap(mBitmap); } @@ -101,20 +104,8 @@ public final class Magnifier { * 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 - * @param scale the scale at which the magnifier zooms on the source content. The - * lower end is clamped to 1 and the higher end to 4 */ - public void show(@FloatRange(from=0) float xPosInView, - @FloatRange(from=0) float yPosInView, - @FloatRange(from=1, to=4) float scale) { - if (scale > 4) { - scale = 4; - } - - if (scale < 1) { - scale = 1; - } - + public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) { if (xPosInView < 0) { xPosInView = 0; } @@ -123,10 +114,6 @@ public final class Magnifier { yPosInView = 0; } - if (mScale != scale) { - resizeBitmap(scale); - } - mScale = scale; configureCoordinates(xPosInView, yPosInView); if (mTimer == null) { @@ -175,11 +162,11 @@ public final class Magnifier { return mWindowWidth; } - private void resizeBitmap(float scale) { - final int bitmapWidth = (int) (mWindowWidth / scale); - final int bitmapHeight = (int) (mWindowHeight / scale); - mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); - getImageView().setImageBitmap(mBitmap); + /** + * @return the zoom scale of the magnifier. + */ + public float getZoomScale() { + return mZoomScale; } private void configureCoordinates(float xPosInView, float yPosInView) { diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java index 5dca8e7f..0cfc1811 100644 --- a/com/android/layoutlib/bridge/Bridge.java +++ b/com/android/layoutlib/bridge/Bridge.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -14,659 +14,62 @@ * limitations under the License. */ -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; +package com.android.layoutlib.bridge;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 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; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; /** - * 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)} + * Legacy Bridge used in the SDK version of layoutlib */ 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 final String ICU_LOCALE_DIRECTION_RTL = "right-to-left"; + private static class BridgeRenderSession extends RenderSession { - public static class StaticMethodNotImplementedException extends RuntimeException { - private static final long serialVersionUID = 1L; + @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 StaticMethodNotImplementedException(String msg) { - super(msg); + return sImage; } - } - - /** - * 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(); - - /** - * 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); + public Result render(long timeout, boolean forceMeasure) { + return NOT_SUPPORTED_RESULT; } @Override - public void error(String tag, String message, Throwable throwable, Object data) { - System.err.println(message); + public Result measure(long timeout) { + return NOT_SUPPORTED_RESULT; } @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 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; + public Result getResult() { + return NOT_SUPPORTED_RESULT; } - - 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); - } + return new BridgeRenderSession(); } @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)); - } + public int getApiLevel() { + return 0; } } diff --git a/com/android/server/AlarmManagerService.java b/com/android/server/AlarmManagerService.java index 4c08f629..3904fc96 100644 --- a/com/android/server/AlarmManagerService.java +++ b/com/android/server/AlarmManagerService.java @@ -63,6 +63,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseLongArray; import android.util.TimeUtils; +import android.util.proto.ProtoOutputStream; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; @@ -190,7 +191,8 @@ class AlarmManagerService extends SystemService { /** * 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. + * used to determine the earliest we can dispatch the next such alarm. Times are in the + * 'elapsed' timebase. */ final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray(); @@ -355,6 +357,22 @@ class AlarmManagerService extends SystemService { TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw); pw.println(); } + + void dumpProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(ConstantsProto.MIN_FUTURITY_DURATION_MS, MIN_FUTURITY); + proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL); + proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT); + proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS, + ALLOW_WHILE_IDLE_SHORT_TIME); + proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS, + ALLOW_WHILE_IDLE_LONG_TIME); + proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS, + ALLOW_WHILE_IDLE_WHITELIST_DURATION); + + proto.end(token); + } } final Constants mConstants; @@ -632,6 +650,20 @@ class AlarmManagerService extends SystemService { b.append('}'); return b.toString(); } + + public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed, + long nowRTC) { + final long token = proto.start(fieldId); + + proto.write(BatchProto.START_REALTIME, start); + proto.write(BatchProto.END_REALTIME, end); + proto.write(BatchProto.FLAGS, flags); + for (Alarm a : alarms) { + a.writeToProto(proto, BatchProto.ALARMS, nowElapsed, nowRTC); + } + + proto.end(token); + } } static class BatchTimeOrder implements Comparator<Batch> { @@ -1007,6 +1039,29 @@ class AlarmManagerService extends SystemService { + ", alarmType=" + mAlarmType + "}"; } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(InFlightProto.UID, mUid); + proto.write(InFlightProto.TAG, mTag); + proto.write(InFlightProto.WHEN_ELAPSED_MS, mWhenElapsed); + proto.write(InFlightProto.ALARM_TYPE, mAlarmType); + if (mPendingIntent != null) { + mPendingIntent.writeToProto(proto, InFlightProto.PENDING_INTENT); + } + if (mBroadcastStats != null) { + mBroadcastStats.writeToProto(proto, InFlightProto.BROADCAST_STATS); + } + if (mFilterStats != null) { + mFilterStats.writeToProto(proto, InFlightProto.FILTER_STATS); + } + if (mWorkSource != null) { + mWorkSource.writeToProto(proto, InFlightProto.WORK_SOURCE); + } + + proto.end(token); + } } static final class FilterStats { @@ -1037,6 +1092,20 @@ class AlarmManagerService extends SystemService { + ", nesting=" + nesting + "}"; } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(FilterStatsProto.TAG, mTag); + proto.write(FilterStatsProto.LAST_FLIGHT_TIME_REALTIME, lastTime); + proto.write(FilterStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime); + proto.write(FilterStatsProto.COUNT, count); + proto.write(FilterStatsProto.WAKEUP_COUNT, numWakeup); + proto.write(FilterStatsProto.START_TIME_REALTIME, startTime); + proto.write(FilterStatsProto.NESTING, nesting); + + proto.end(token); + } } static final class BroadcastStats { @@ -1067,6 +1136,20 @@ class AlarmManagerService extends SystemService { + ", nesting=" + nesting + "}"; } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(BroadcastStatsProto.UID, mUid); + proto.write(BroadcastStatsProto.PACKAGE_NAME, mPackageName); + proto.write(BroadcastStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime); + proto.write(BroadcastStatsProto.COUNT, count); + proto.write(BroadcastStatsProto.WAKEUP_COUNT, numWakeup); + proto.write(BroadcastStatsProto.START_TIME_REALTIME, startTime); + proto.write(BroadcastStatsProto.NESTING, nesting); + + proto.end(token); + } } final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats @@ -1128,14 +1211,14 @@ class AlarmManagerService extends SystemService { | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent, Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL); - + // now that we have initied the driver schedule the alarm mClockReceiver = new ClockReceiver(); mClockReceiver.scheduleTimeTickEvent(); mClockReceiver.scheduleDateChangedEvent(); mInteractiveStateReceiver = new InteractiveStateReceiver(); mUninstallReceiver = new UninstallReceiver(); - + if (mNativeData != 0) { AlarmThread waitThread = new AlarmThread(); waitThread.start(); @@ -1568,7 +1651,12 @@ class AlarmManagerService extends SystemService { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return; - dumpImpl(pw); + + if (args.length > 0 && "--proto".equals(args[0])) { + dumpProto(fd); + } else { + dumpImpl(pw); + } } }; @@ -1681,7 +1769,7 @@ class AlarmManagerService extends SystemService { pw.print(" Idling until: "); if (mPendingIdleUntil != null) { pw.println(mPendingIdleUntil); - mPendingIdleUntil.dump(pw, " ", nowRTC, nowELAPSED, sdf); + mPendingIdleUntil.dump(pw, " ", nowELAPSED, nowRTC, sdf); } else { pw.println("null"); } @@ -1691,7 +1779,7 @@ class AlarmManagerService extends SystemService { if (mNextWakeFromIdle != null) { pw.println(); pw.print(" Next wake from idle: "); pw.println(mNextWakeFromIdle); - mNextWakeFromIdle.dump(pw, " ", nowRTC, nowELAPSED, sdf); + mNextWakeFromIdle.dump(pw, " ", nowELAPSED, nowRTC, sdf); } pw.println(); @@ -1760,6 +1848,7 @@ class AlarmManagerService extends SystemService { } }; int len = 0; + // Get the top 10 FilterStats, ordered by aggregateTime. for (int iu=0; iu<mBroadcastStats.size(); iu++) { ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu); for (int ip=0; ip<uidStats.size(); ip++) { @@ -1886,6 +1975,244 @@ class AlarmManagerService extends SystemService { } } + void dumpProto(FileDescriptor fd) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + + synchronized (mLock) { + final long nowRTC = System.currentTimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + proto.write(AlarmManagerServiceProto.CURRENT_TIME, nowRTC); + proto.write(AlarmManagerServiceProto.ELAPSED_REALTIME, nowElapsed); + proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_CLOCK_TIME, + mLastTimeChangeClockTime); + proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_REALTIME, + mLastTimeChangeRealtime); + + 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); + } + + proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive); + if (!mInteractive) { + // Durations + proto.write(AlarmManagerServiceProto.TIME_SINCE_NON_INTERACTIVE_MS, + nowElapsed - mNonInteractiveStartTime); + proto.write(AlarmManagerServiceProto.MAX_WAKEUP_DELAY_MS, + currentNonWakeupFuzzLocked(nowElapsed)); + proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_DISPATCH_MS, + nowElapsed - mLastAlarmDeliveryTime); + proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_DELIVERY_MS, + nowElapsed - mNextNonWakeupDeliveryTime); + } + + proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_ALARM_MS, + mNextNonWakeup - nowElapsed); + proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_WAKEUP_MS, + mNextWakeup - nowElapsed); + proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_MS, + nowElapsed - mLastWakeup); + 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(); + for (int i = 0; i < nextAlarmClockForUserSize; i++) { + users.add(mNextAlarmClockForUser.keyAt(i)); + } + final int pendingSendNextAlarmClockChangedForUserSize = + mPendingSendNextAlarmClockChangedForUser.size(); + for (int i = 0; i < pendingSendNextAlarmClockChangedForUserSize; i++) { + users.add(mPendingSendNextAlarmClockChangedForUser.keyAt(i)); + } + for (int user : users) { + final AlarmManager.AlarmClockInfo next = mNextAlarmClockForUser.get(user); + final long time = next != null ? next.getTriggerTime() : 0; + final boolean pendingSend = mPendingSendNextAlarmClockChangedForUser.get(user); + final long aToken = proto.start(AlarmManagerServiceProto.NEXT_ALARM_CLOCK_METADATA); + proto.write(AlarmClockMetadataProto.USER, user); + proto.write(AlarmClockMetadataProto.IS_PENDING_SEND, pendingSend); + proto.write(AlarmClockMetadataProto.TRIGGER_TIME_MS, time); + proto.end(aToken); + } + for (Batch b : mAlarmBatches) { + b.writeToProto(proto, AlarmManagerServiceProto.PENDING_ALARM_BATCHES, + nowElapsed, nowRTC); + } + for (int i = 0; i < mPendingBackgroundAlarms.size(); i++) { + final ArrayList<Alarm> blockedAlarms = mPendingBackgroundAlarms.valueAt(i); + if (blockedAlarms != null) { + for (Alarm a : blockedAlarms) { + a.writeToProto(proto, + AlarmManagerServiceProto.PENDING_USER_BLOCKED_BACKGROUND_ALARMS, + nowElapsed, nowRTC); + } + } + } + if (mPendingIdleUntil != null) { + mPendingIdleUntil.writeToProto( + proto, AlarmManagerServiceProto.PENDING_IDLE_UNTIL, nowElapsed, nowRTC); + } + for (Alarm a : mPendingWhileIdleAlarms) { + a.writeToProto(proto, AlarmManagerServiceProto.PENDING_WHILE_IDLE_ALARMS, + nowElapsed, nowRTC); + } + if (mNextWakeFromIdle != null) { + mNextWakeFromIdle.writeToProto(proto, AlarmManagerServiceProto.NEXT_WAKE_FROM_IDLE, + nowElapsed, nowRTC); + } + + for (Alarm a : mPendingNonWakeupAlarms) { + a.writeToProto(proto, AlarmManagerServiceProto.PAST_DUE_NON_WAKEUP_ALARMS, + nowElapsed, nowRTC); + } + + proto.write(AlarmManagerServiceProto.DELAYED_ALARM_COUNT, mNumDelayedAlarms); + proto.write(AlarmManagerServiceProto.TOTAL_DELAY_TIME_MS, mTotalDelayTime); + proto.write(AlarmManagerServiceProto.MAX_DELAY_DURATION_MS, mMaxDelayTime); + proto.write(AlarmManagerServiceProto.MAX_NON_INTERACTIVE_DURATION_MS, + mNonInteractiveTime); + + proto.write(AlarmManagerServiceProto.BROADCAST_REF_COUNT, mBroadcastRefCount); + proto.write(AlarmManagerServiceProto.PENDING_INTENT_SEND_COUNT, mSendCount); + proto.write(AlarmManagerServiceProto.PENDING_INTENT_FINISH_COUNT, mSendFinishCount); + proto.write(AlarmManagerServiceProto.LISTENER_SEND_COUNT, mListenerCount); + proto.write(AlarmManagerServiceProto.LISTENER_FINISH_COUNT, mListenerFinishCount); + + for (InFlight f : mInFlight) { + f.writeToProto(proto, AlarmManagerServiceProto.OUTSTANDING_DELIVERIES); + } + + proto.write(AlarmManagerServiceProto.ALLOW_WHILE_IDLE_MIN_DURATION_MS, + mAllowWhileIdleMinTime); + for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) { + final long token = proto.start( + AlarmManagerServiceProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES); + proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.UID, + mLastAllowWhileIdleDispatch.keyAt(i)); + proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.TIME_MS, + mLastAllowWhileIdleDispatch.valueAt(i)); + proto.end(token); + } + + mLog.writeToProto(proto, AlarmManagerServiceProto.RECENT_PROBLEMS); + + final FilterStats[] topFilters = new FilterStats[10]; + final Comparator<FilterStats> comparator = new Comparator<FilterStats>() { + @Override + public int compare(FilterStats lhs, FilterStats rhs) { + if (lhs.aggregateTime < rhs.aggregateTime) { + return 1; + } else if (lhs.aggregateTime > rhs.aggregateTime) { + return -1; + } + return 0; + } + }; + int len = 0; + // Get the top 10 FilterStats, ordered by aggregateTime. + for (int iu = 0; iu < mBroadcastStats.size(); ++iu) { + ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu); + for (int ip = 0; ip < uidStats.size(); ++ip) { + BroadcastStats bs = uidStats.valueAt(ip); + for (int is = 0; is < bs.filterStats.size(); ++is) { + FilterStats fs = bs.filterStats.valueAt(is); + int pos = len > 0 + ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0; + if (pos < 0) { + pos = -pos - 1; + } + if (pos < topFilters.length) { + int copylen = topFilters.length - pos - 1; + if (copylen > 0) { + System.arraycopy(topFilters, pos, topFilters, pos+1, copylen); + } + topFilters[pos] = fs; + if (len < topFilters.length) { + len++; + } + } + } + } + } + for (int i = 0; i < len; ++i) { + final long token = proto.start(AlarmManagerServiceProto.TOP_ALARMS); + FilterStats fs = topFilters[i]; + + proto.write(AlarmManagerServiceProto.TopAlarm.UID, fs.mBroadcastStats.mUid); + proto.write(AlarmManagerServiceProto.TopAlarm.PACKAGE_NAME, + fs.mBroadcastStats.mPackageName); + fs.writeToProto(proto, AlarmManagerServiceProto.TopAlarm.FILTER); + + proto.end(token); + } + + final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>(); + for (int iu = 0; iu < mBroadcastStats.size(); ++iu) { + ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu); + for (int ip = 0; ip < uidStats.size(); ++ip) { + final long token = proto.start(AlarmManagerServiceProto.ALARM_STATS); + + BroadcastStats bs = uidStats.valueAt(ip); + bs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.BROADCAST); + + // uidStats is an ArrayMap, which we can't sort. + tmpFilters.clear(); + for (int is = 0; is < bs.filterStats.size(); ++is) { + tmpFilters.add(bs.filterStats.valueAt(is)); + } + Collections.sort(tmpFilters, comparator); + for (FilterStats fs : tmpFilters) { + fs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.FILTERS); + } + + proto.end(token); + } + } + + if (RECORD_DEVICE_IDLE_ALARMS) { + for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) { + IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i); + final long token = proto.start( + AlarmManagerServiceProto.ALLOW_WHILE_IDLE_DISPATCHES); + + proto.write(IdleDispatchEntryProto.UID, ent.uid); + proto.write(IdleDispatchEntryProto.PKG, ent.pkg); + proto.write(IdleDispatchEntryProto.TAG, ent.tag); + proto.write(IdleDispatchEntryProto.OP, ent.op); + proto.write(IdleDispatchEntryProto.ENTRY_CREATION_REALTIME, + ent.elapsedRealtime); + proto.write(IdleDispatchEntryProto.ARG_REALTIME, ent.argRealtime); + + proto.end(token); + } + } + + if (WAKEUP_STATS) { + for (WakeupEvent event : mRecentWakeups) { + final long token = proto.start(AlarmManagerServiceProto.RECENT_WAKEUP_HISTORY); + proto.write(WakeupEventProto.UID, event.uid); + proto.write(WakeupEventProto.ACTION, event.action); + proto.write(WakeupEventProto.WHEN, event.when); + proto.end(token); + } + } + } + + proto.flush(); + } + private void logBatchesLocked(SimpleDateFormat sdf) { ByteArrayOutputStream bs = new ByteArrayOutputStream(2048); PrintWriter pw = new PrintWriter(bs); @@ -2328,24 +2655,24 @@ class AlarmManagerService extends SystemService { alarmSeconds = when / 1000; alarmNanoseconds = (when % 1000) * 1000 * 1000; } - + set(mNativeData, type, alarmSeconds, alarmNanoseconds); } else { Message msg = Message.obtain(); msg.what = ALARM_EVENT; - + mHandler.removeMessages(ALARM_EVENT); mHandler.sendMessageAtTime(msg, when); } } private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, - String prefix, String label, long nowRTC, long nowELAPSED, SimpleDateFormat sdf) { + String prefix, String label, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) { for (int i=list.size()-1; i>=0; i--) { Alarm a = list.get(i); pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); pw.print(": "); pw.println(a); - a.dump(pw, prefix + " ", nowRTC, nowELAPSED, sdf); + a.dump(pw, prefix + " ", nowELAPSED, nowRTC, sdf); } } @@ -2355,8 +2682,6 @@ class AlarmManagerService extends SystemService { case RTC_WAKEUP : return "RTC_WAKEUP"; case ELAPSED_REALTIME : return "ELAPSED"; case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP"; - default: - break; } return "--unknown--"; } @@ -2368,7 +2693,7 @@ class AlarmManagerService extends SystemService { final String label = labelForType(a.type); pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); pw.print(": "); pw.println(a); - a.dump(pw, prefix + " ", nowRTC, nowELAPSED, sdf); + a.dump(pw, prefix + " ", nowELAPSED, nowRTC, sdf); } } @@ -2533,7 +2858,7 @@ class AlarmManagerService extends SystemService { return 0; } } - + private static class Alarm { public final int type; public final long origWhen; @@ -2627,7 +2952,7 @@ class AlarmManagerService extends SystemService { return sb.toString(); } - public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED, + public void dump(PrintWriter pw, String prefix, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); pw.print(prefix); pw.print("tag="); pw.println(statsTag); @@ -2656,6 +2981,30 @@ class AlarmManagerService extends SystemService { pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder()); } } + + public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed, + long nowRTC) { + final long token = proto.start(fieldId); + + proto.write(AlarmProto.TAG, statsTag); + proto.write(AlarmProto.TYPE, type); + proto.write(AlarmProto.WHEN_ELAPSED_MS, whenElapsed - nowElapsed); + proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength); + proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval); + proto.write(AlarmProto.COUNT, count); + proto.write(AlarmProto.FLAGS, flags); + if (alarmClock != null) { + alarmClock.writeToProto(proto, AlarmProto.ALARM_CLOCK); + } + if (operation != null) { + operation.writeToProto(proto, AlarmProto.OPERATION); + } + if (listener != null) { + proto.write(AlarmProto.LISTENER, listener.asBinder().toString()); + } + + proto.end(token); + } } void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) { @@ -2752,7 +3101,7 @@ class AlarmManagerService extends SystemService { { super("AlarmManager"); } - + public void run() { ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); @@ -2918,10 +3267,10 @@ class AlarmManagerService extends SystemService { public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2; public static final int LISTENER_TIMEOUT = 3; public static final int REPORT_ALARMS_ACTIVE = 4; - + public AlarmHandler() { } - + public void handleMessage(Message msg) { switch (msg.what) { case ALARM_EVENT: { @@ -2969,7 +3318,7 @@ class AlarmManagerService extends SystemService { } } } - + class ClockReceiver extends BroadcastReceiver { public ClockReceiver() { IntentFilter filter = new IntentFilter(); @@ -2977,7 +3326,7 @@ class AlarmManagerService extends SystemService { filter.addAction(Intent.ACTION_DATE_CHANGED); getContext().registerReceiver(this, filter); } - + @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) { @@ -2996,7 +3345,7 @@ class AlarmManagerService extends SystemService { scheduleDateChangedEvent(); } } - + public void scheduleTimeTickEvent() { final long currentTime = System.currentTimeMillis(); final long nextTime = 60000 * ((currentTime / 60000) + 1); @@ -3026,7 +3375,7 @@ class AlarmManagerService extends SystemService { Process.myUid(), "android"); } } - + class InteractiveStateReceiver extends BroadcastReceiver { public InteractiveStateReceiver() { IntentFilter filter = new IntentFilter(); @@ -3059,7 +3408,7 @@ class AlarmManagerService extends SystemService { sdFilter.addAction(Intent.ACTION_UID_REMOVED); getContext().registerReceiver(this, sdFilter); } - + @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java index 46b671bd..68546bd2 100644 --- a/com/android/server/BatteryService.java +++ b/com/android/server/BatteryService.java @@ -43,6 +43,7 @@ import android.hardware.health.V2_0.IHealthInfoCallback; import android.hardware.health.V2_0.IHealth; import android.hardware.health.V2_0.Result; import android.os.BatteryManager; +import android.os.BatteryManagerProto; import android.os.BatteryManagerInternal; import android.os.BatteryProperty; import android.os.Binder; @@ -252,39 +253,33 @@ public final class BatteryService extends SystemService { mHealthServiceWrapper.init(mHealthHalCallback, new HealthServiceWrapper.IServiceManagerSupplier() {}, new HealthServiceWrapper.IHealthSupplier() {}); - } catch (RemoteException | NoSuchElementException ex) { - Slog.w(TAG, "health: cannot register callback. " - + "BatteryService will be started with dummy values. Reason: " - + ex.getClass().getSimpleName() + ": " + ex.getMessage()); - update(new HealthInfo()); - return; + } catch (RemoteException ex) { + Slog.e(TAG, "health: cannot register callback. (RemoteException)"); + throw ex.rethrowFromSystemServer(); + } catch (NoSuchElementException ex) { + Slog.e(TAG, "health: cannot register callback. (no supported health HAL service)"); + throw ex; } // init register for new service notifications, and IServiceManager should return the // existing service in a near future. Wait for this.update() to instantiate // the initial mHealthInfo. - long timeWaited = 0; + long beforeWait = SystemClock.uptimeMillis(); synchronized (mLock) { - long beforeWait = SystemClock.uptimeMillis(); - while (mHealthInfo == null && - (timeWaited = SystemClock.uptimeMillis() - beforeWait) < HEALTH_HAL_WAIT_MS) { + while (mHealthInfo == null) { + Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait) + + "ms for callbacks. Waiting another " + HEALTH_HAL_WAIT_MS + " ms..."); try { - mLock.wait(HEALTH_HAL_WAIT_MS - timeWaited); + mLock.wait(HEALTH_HAL_WAIT_MS); } catch (InterruptedException ex) { - break; + Slog.i(TAG, "health: InterruptedException when waiting for update. " + + " Continuing..."); } } - if (mHealthInfo == null) { - Slog.w(TAG, "health: Waited " + timeWaited + "ms for callbacks but received " - + "nothing. BatteryService will be started with dummy values."); - update(new HealthInfo()); - return; - } } - if (DEBUG) { - Slog.d(TAG, "health: Waited " + timeWaited + "ms and received the update."); - } + Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait) + + "ms and received the update."); } private void updateBatteryWarningLevelLocked() { @@ -913,13 +908,13 @@ public final class BatteryService extends SystemService { synchronized (mLock) { proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped); - int batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_NONE; + int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE; if (mHealthInfo.legacy.chargerAcOnline) { - batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_AC; + batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC; } else if (mHealthInfo.legacy.chargerUsbOnline) { - batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_USB; + batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB; } else if (mHealthInfo.legacy.chargerWirelessOnline) { - batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_WIRELESS; + batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS; } proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue); proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent); @@ -1060,6 +1055,7 @@ public final class BatteryService extends SystemService { } public int getProperty(int id, final BatteryProperty prop) throws RemoteException { IHealth service = mHealthServiceWrapper.getLastService(); + if (service == null) throw new RemoteException("no health service"); final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED); switch(id) { case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER: @@ -1101,8 +1097,10 @@ public final class BatteryService extends SystemService { } return outResult.value; } - public void scheduleUpdate() { - Slog.e(TAG, "health: must not call scheduleUpdate on battery properties"); + public void scheduleUpdate() throws RemoteException { + IHealth service = mHealthServiceWrapper.getLastService(); + if (service == null) throw new RemoteException("no health service"); + service.update(); } } diff --git a/com/android/server/ConnectivityService.java b/com/android/server/ConnectivityService.java index 348c7997..7e65d360 100644 --- a/com/android/server/ConnectivityService.java +++ b/com/android/server/ConnectivityService.java @@ -70,7 +70,6 @@ import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.UidRange; import android.net.Uri; -import android.net.metrics.DefaultNetworkEvent; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.util.MultinetworkPolicyTracker; @@ -127,6 +126,7 @@ import com.android.internal.util.WakeupMessage; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; +import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.LingerMonitor; import com.android.server.connectivity.MockableSystemProperties; @@ -143,6 +143,7 @@ import com.android.server.connectivity.tethering.TetheringDependencies; import com.android.server.net.BaseNetworkObserver; import com.android.server.net.LockdownVpnTracker; import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.utils.PriorityDump; import com.google.android.collect.Lists; @@ -682,6 +683,28 @@ public class ConnectivityService extends IConnectivityManager.Stub } private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(); + /** + * Helper class which parses out priority arguments and dumps sections according to their + * priority. If priority arguments are omitted, function calls the legacy dump command. + */ + private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { + @Override + public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + doDump(fd, pw, new String[] {DIAG_ARG}, asProto); + doDump(fd, pw, new String[] {SHORT_ARG}, asProto); + } + + @Override + public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + doDump(fd, pw, args, asProto); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + doDump(fd, pw, args, asProto); + } + }; + public ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { this(context, netManager, statsService, policyManager, new IpConnectivityLog()); @@ -1862,8 +1885,13 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + PriorityDump.dump(mPriorityDumper, fd, writer, args); + } + + private void doDump(FileDescriptor fd, PrintWriter writer, String[] args, boolean asProto) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + if (asProto) return; if (argsContain(args, DIAG_ARG)) { dumpNetworkDiagnostics(pw); @@ -2265,7 +2293,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Let rematchAllNetworksAndRequests() below record a new default network event // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence // whose timestamps tell how long it takes to recover a default network. - logDefaultNetworkEvent(null, nai); + metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai); } notifyIfacesChangedForNetworkStats(); // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied @@ -4995,7 +5023,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // Notify system services that this network is up. makeDefault(newNetwork); // Log 0 -> X and Y -> X default network transitions, where X is the new default. - logDefaultNetworkEvent(newNetwork, oldDefaultNetwork); + metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent( + newNetwork, oldDefaultNetwork); // Have a new default network, release the transition wakelock in scheduleReleaseNetworkTransitionWakelock(); } @@ -5554,25 +5583,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return ServiceManager.checkService(name) != null; } - private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) { - int newNetid = NETID_UNSET; - int prevNetid = NETID_UNSET; - int[] transports = new int[0]; - boolean hadIPv4 = false; - boolean hadIPv6 = false; - - if (newNai != null) { - newNetid = newNai.network.netId; - transports = newNai.networkCapabilities.getTransportTypes(); - } - if (prevNai != null) { - prevNetid = prevNai.network.netId; - final LinkProperties lp = prevNai.linkProperties; - hadIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute(); - hadIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute(); - } - - mMetricsLog.log(new DefaultNetworkEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6)); + @VisibleForTesting + protected IpConnectivityMetrics.Logger metricsLogger() { + return checkNotNull(LocalServices.getService(IpConnectivityMetrics.Logger.class), + "no IpConnectivityMetrics service"); } private void logNetworkEvent(NetworkAgentInfo nai, int evtype) { diff --git a/com/android/server/InputMethodManagerService.java b/com/android/server/InputMethodManagerService.java index 9da37570..f007bcc8 100644 --- a/com/android/server/InputMethodManagerService.java +++ b/com/android/server/InputMethodManagerService.java @@ -1470,7 +1470,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER); mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter); - buildInputMethodListLocked(true /* resetDefaultEnabledIme */); + final String defaultImiId = mSettings.getSelectedInputMethod(); + final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); + buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */); resetDefaultImeLocked(mContext); updateFromSettingsLocked(true); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, diff --git a/com/android/server/IpSecService.java b/com/android/server/IpSecService.java index cf1d33c8..1154fbe6 100644 --- a/com/android/server/IpSecService.java +++ b/com/android/server/IpSecService.java @@ -754,7 +754,7 @@ public class IpSecService extends IIpSecService.Stub { * and re-binding, during which the system could *technically* hand that port out to someone * else. */ - private void bindToRandomPort(FileDescriptor sockFd) throws IOException { + private int bindToRandomPort(FileDescriptor sockFd) throws IOException { for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) { try { FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); @@ -763,7 +763,7 @@ public class IpSecService extends IIpSecService.Stub { Os.close(probeSocket); Log.v(TAG, "Binding to port " + port); Os.bind(sockFd, INADDR_ANY, port); - return; + return port; } catch (ErrnoException e) { // Someone miraculously claimed the port just after we closed probeSocket. if (e.errno == OsConstants.EADDRINUSE) { @@ -803,7 +803,7 @@ public class IpSecService extends IIpSecService.Stub { Log.v(TAG, "Binding to port " + port); Os.bind(sockFd, INADDR_ANY, port); } else { - bindToRandomPort(sockFd); + port = bindToRandomPort(sockFd); } // This code is common to both the unspecified and specified port cases Os.setsockoptInt( @@ -944,13 +944,13 @@ public class IpSecService extends IIpSecService.Stub { (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0, spi, (auth != null) ? auth.getName() : "", - (auth != null) ? auth.getKey() : null, + (auth != null) ? auth.getKey() : new byte[] {}, (auth != null) ? auth.getTruncationLengthBits() : 0, (crypt != null) ? crypt.getName() : "", - (crypt != null) ? crypt.getKey() : null, + (crypt != null) ? crypt.getKey() : new byte[] {}, (crypt != null) ? crypt.getTruncationLengthBits() : 0, (authCrypt != null) ? authCrypt.getName() : "", - (authCrypt != null) ? authCrypt.getKey() : null, + (authCrypt != null) ? authCrypt.getKey() : new byte[] {}, (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0, encapType, encapLocalPort, diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java index cd256109..bfbce408 100644 --- a/com/android/server/StorageManagerService.java +++ b/com/android/server/StorageManagerService.java @@ -641,8 +641,8 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon break; } case H_PARTITION_FORGET: { - final String partGuid = (String) msg.obj; - forgetPartition(partGuid); + final VolumeRecord rec = (VolumeRecord) msg.obj; + forgetPartition(rec.partGuid, rec.fsUuid); break; } case H_RESET: { @@ -1694,7 +1694,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon synchronized (mLock) { final VolumeRecord rec = mRecords.remove(fsUuid); if (rec != null && !TextUtils.isEmpty(rec.partGuid)) { - mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget(); + mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget(); } mCallbacks.notifyVolumeForgotten(fsUuid); @@ -1718,7 +1718,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon final String fsUuid = mRecords.keyAt(i); final VolumeRecord rec = mRecords.valueAt(i); if (!TextUtils.isEmpty(rec.partGuid)) { - mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget(); + mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget(); } mCallbacks.notifyVolumeForgotten(fsUuid); } @@ -1733,9 +1733,9 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon } } - private void forgetPartition(String partGuid) { + private void forgetPartition(String partGuid, String fsUuid) { try { - mVold.forgetPartition(partGuid); + mVold.forgetPartition(partGuid, fsUuid); } catch (Exception e) { Slog.wtf(TAG, e); } diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java index d9db22e9..de5d879a 100644 --- a/com/android/server/SystemServer.java +++ b/com/android/server/SystemServer.java @@ -125,7 +125,10 @@ import java.util.Timer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; -import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; +import static android.os.IServiceManager.DUMP_FLAG_PROTO; import static android.view.Display.DEFAULT_DISPLAY; public final class SystemServer { @@ -827,7 +830,7 @@ public final class SystemServer { mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore, new PhoneWindowManager()); ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false, - DUMP_PRIORITY_CRITICAL); + DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); traceEnd(); @@ -1138,7 +1141,9 @@ public final class SystemServer { try { connectivity = new ConnectivityService( context, networkManagement, networkStats, networkPolicy); - ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity); + ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity, + /* allowIsolated= */ false, + DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); networkStats.bindConnectivityManager(connectivity); networkPolicy.bindConnectivityManager(connectivity); } catch (Throwable e) { @@ -1551,6 +1556,11 @@ public final class SystemServer { traceEnd(); } + // Statsd helper + traceBeginAndSlog("StartStatsCompanionService"); + mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class); + traceEnd(); + // Before things start rolling, be sure we have decided whether // we are in safe mode. final boolean safeMode = wm.detectSafeMode(); diff --git a/com/android/server/accounts/AccountManagerService.java b/com/android/server/accounts/AccountManagerService.java index c6840328..0d4f5cb8 100644 --- a/com/android/server/accounts/AccountManagerService.java +++ b/com/android/server/accounts/AccountManagerService.java @@ -5101,8 +5101,16 @@ public class AccountManagerService logStatement.bindLong(4, callingUid); logStatement.bindString(5, tableName); logStatement.bindLong(6, userDebugDbInsertionPoint); - logStatement.execute(); - logStatement.clearBindings(); + try { + logStatement.execute(); + } catch (IllegalStateException e) { + // Guard against crash, DB can already be closed + // since this statement is executed on a handler thread + Slog.w(TAG, "Failed to insert a log record. accountId=" + accountId + + " action=" + action + " tableName=" + tableName + " Error: " + e); + } finally { + logStatement.clearBindings(); + } } } diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java index 089db871..c04ddf8a 100644 --- a/com/android/server/am/ActivityDisplay.java +++ b/com/android/server/am/ActivityDisplay.java @@ -38,7 +38,9 @@ import static com.android.server.am.proto.ActivityDisplayProto.CONFIGURATION_CON import static com.android.server.am.proto.ActivityDisplayProto.STACKS; import static com.android.server.am.proto.ActivityDisplayProto.ID; +import android.annotation.Nullable; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.util.IntArray; import android.util.Slog; @@ -206,6 +208,18 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } /** + * Returns an existing stack compatible with the input params or creates one + * if a compatible stack doesn't exist. + * @see #getOrCreateStack(int, int, boolean) + */ + <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r, + @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType, + boolean onTop) { + final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType); + return getOrCreateStack(windowingMode, activityType, onTop); + } + + /** * Creates a stack matching the input windowing mode and activity type on this display. * @param windowingMode The windowing mode the stack should be created in. If * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will @@ -235,7 +249,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } final ActivityManagerService service = mSupervisor.mService; - if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow, + if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow, service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement, service.mSupportsPictureInPicture, activityType)) { throw new IllegalArgumentException("Can't create stack for unsupported windowingMode=" @@ -252,35 +266,29 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } - windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType); - final int stackId = mSupervisor.getNextStackId(); + return createStackUnchecked(windowingMode, activityType, stackId, onTop); + } - final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop); + @VisibleForTesting + <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType, + int stackId, boolean onTop) { + if (windowingMode == WINDOWING_MODE_PINNED) { + return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop); + } + final T stack = (T) new ActivityStack( + this, stackId, mSupervisor, windowingMode, activityType, onTop); if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // Make sure recents stack exist when creating a dock stack as it normally need to be on - // the other side of the docked stack and we make visibility decisions based on that. + // 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 on that. // TODO: Not sure if this is needed after we change to calculate visibility based on // stack z-order vs. id. getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop); } - return stack; } - @VisibleForTesting - <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType, - int stackId, boolean onTop) { - switch (windowingMode) { - case WINDOWING_MODE_PINNED: - return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop); - default: - return (T) new ActivityStack( - this, stackId, mSupervisor, windowingMode, activityType, onTop); - } - } - /** * Removes stacks in the input windowing modes from the system if they are of activity type * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED @@ -372,6 +380,105 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } + /** + * Returns true if the {@param windowingMode} is supported based on other parameters passed in. + * @param windowingMode The windowing mode we are checking support for. + * @param supportsMultiWindow If we should consider support for multi-window mode in general. + * @param supportsSplitScreen If we should consider support for split-screen multi-window. + * @param supportsFreeform If we should consider support for freeform multi-window. + * @param supportsPip If we should consider support for picture-in-picture mutli-window. + * @param activityType The activity type under consideration. + * @return true if the windowing mode is supported. + */ + private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow, + boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip, + int activityType) { + + if (windowingMode == WINDOWING_MODE_UNDEFINED + || windowingMode == WINDOWING_MODE_FULLSCREEN) { + return true; + } + if (!supportsMultiWindow) { + return false; + } + + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { + return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode( + windowingMode, activityType); + } + + if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) { + return false; + } + + if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) { + return false; + } + return true; + } + + int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options, + @Nullable TaskRecord task, int activityType) { + + // First preference if the windowing mode in the activity options if set. + int windowingMode = (options != null) + ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED; + + // If windowing mode is unset, then next preference is the candidate task, then the + // activity record. + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + if (task != null) { + windowingMode = task.getWindowingMode(); + } + if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) { + windowingMode = r.getWindowingMode(); + } + if (windowingMode == WINDOWING_MODE_UNDEFINED) { + // Use the display's windowing mode. + windowingMode = getWindowingMode(); + } + } + + // Make sure the windowing mode we are trying to use makes sense for what is supported. + final ActivityManagerService service = mSupervisor.mService; + boolean supportsMultiWindow = service.mSupportsMultiWindow; + boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow; + boolean supportsFreeform = service.mSupportsFreeformWindowManagement; + boolean supportsPip = service.mSupportsPictureInPicture; + if (supportsMultiWindow) { + if (task != null) { + supportsMultiWindow = task.isResizeable(); + supportsSplitScreen = task.supportsSplitScreenWindowingMode(); + // TODO: Do we need to check for freeform and Pip support here? + } else if (r != null) { + supportsMultiWindow = r.isResizeable(); + supportsSplitScreen = r.supportsSplitScreenWindowingMode(); + supportsFreeform = r.supportsFreeform(); + supportsPip = r.supportsPictureInPicture(); + } + } + + final boolean inSplitScreenMode = hasSplitScreenPrimaryStack(); + if (!inSplitScreenMode + && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { + // Switch to fullscreen windowing mode if we are not in split-screen mode and we are + // trying to launch in split-screen secondary. + windowingMode = WINDOWING_MODE_FULLSCREEN; + } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN + && supportsSplitScreen) { + windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + } + + if (windowingMode != WINDOWING_MODE_UNDEFINED + && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen, + supportsFreeform, supportsPip, activityType)) { + return windowingMode; + } + // Return the display's windowing mode + return getWindowingMode(); + } + /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */ int getTopVisibleStackActivityType(int excludeWindowingMode) { for (int i = mStacks.size() - 1; i >= 0; --i) { @@ -408,6 +515,14 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } + /** We are in the process of exiting split-screen mode. */ + void onExitingSplitScreenMode() { + // Remove reference to the primary-split-screen stack so it no longer has any effect on the + // display. For example, we want to be able to create fullscreen stack for standard activity + // types when exiting split-screen mode. + mSplitScreenPrimaryStack = null; + } + ActivityStack getSplitScreenPrimaryStack() { return mSplitScreenPrimaryStack; } @@ -424,21 +539,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { return mPinnedStack != null; } - int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) { - final boolean inSplitScreenMode = hasSplitScreenPrimaryStack(); - if (!inSplitScreenMode - && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) { - // Switch to fullscreen windowing mode if we are not in split-screen mode and we are - // trying to launch in split-screen secondary. - return WINDOWING_MODE_FULLSCREEN; - } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN - && WindowConfiguration.supportSplitScreenWindowingMode( - windowingMode, activityType)) { - return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - } - return windowingMode; - } - @Override public String toString() { return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}"; @@ -504,7 +604,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - super.writeToProto(proto, CONFIGURATION_CONTAINER); + super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); proto.write(ID, mDisplayId); for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = mStacks.get(stackNdx); diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java index e1e53b3b..f2e04932 100644 --- a/com/android/server/am/ActivityManagerService.java +++ b/com/android/server/am/ActivityManagerService.java @@ -23,16 +23,20 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.Manifest.permission.READ_FRAME_BUFFER; +import static android.Manifest.permission.REMOVE_TASKS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; 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_UNDEFINED; import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY; @@ -49,9 +53,10 @@ import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground; import static android.os.Build.VERSION_CODES.N; -import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL; -import static android.os.IServiceManager.DUMP_PRIORITY_HIGH; -import static android.os.IServiceManager.DUMP_PRIORITY_NORMAL; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; +import static android.os.IServiceManager.DUMP_FLAG_PROTO; import static android.os.Process.BLUETOOTH_UID; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.FIRST_ISOLATED_UID; @@ -226,6 +231,8 @@ import android.app.PictureInPictureParams; import android.app.ProfilerInfo; import android.app.RemoteAction; import android.app.WaitResult; +import android.app.WindowConfiguration.ActivityType; +import android.app.WindowConfiguration.WindowingMode; import android.app.admin.DevicePolicyManager; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; @@ -333,6 +340,7 @@ import android.text.style.SuggestionSpan; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.StatsLog; import android.util.TimingsTraceLog; import android.util.DebugUtils; import android.util.DisplayMetrics; @@ -368,6 +376,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.BinderInternal; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; @@ -665,7 +674,7 @@ public class ActivityManagerService extends IActivityManager.Stub /** * List of intents that were used to start the most recent tasks. */ - final RecentTasks mRecentTasks; + private final RecentTasks mRecentTasks; /** * For addAppTask: cached of the last activity component that was added. @@ -714,30 +723,36 @@ public class ActivityManagerService extends IActivityManager.Stub */ private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { @Override - public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) { - doDump(fd, pw, new String[] {"activities"}); + public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, + boolean asProto) { + if (asProto) return; + doDump(fd, pw, new String[]{"activities"}, asProto); } @Override - public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) { - doDump(fd, pw, new String[] {"settings"}); - doDump(fd, pw, new String[] {"intents"}); - doDump(fd, pw, new String[] {"broadcasts"}); - doDump(fd, pw, new String[] {"providers"}); - doDump(fd, pw, new String[] {"permissions"}); - doDump(fd, pw, new String[] {"services"}); - doDump(fd, pw, new String[] {"recents"}); - doDump(fd, pw, new String[] {"lastanr"}); - doDump(fd, pw, new String[] {"starter"}); - if (mAssociations.size() > 0) { - doDump(fd, pw, new String[] {"associations"}); - } - doDump(fd, pw, new String[] {"processes"}); + public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + if (asProto) { + doDump(fd, pw, new String[0], asProto); + } else { + doDump(fd, pw, new String[]{"settings"}, asProto); + doDump(fd, pw, new String[]{"intents"}, asProto); + doDump(fd, pw, new String[]{"broadcasts"}, asProto); + doDump(fd, pw, new String[]{"providers"}, asProto); + doDump(fd, pw, new String[]{"permissions"}, asProto); + doDump(fd, pw, new String[]{"services"}, asProto); + doDump(fd, pw, new String[]{"recents"}, asProto); + doDump(fd, pw, new String[]{"lastanr"}, asProto); + doDump(fd, pw, new String[]{"starter"}, asProto); + if (mAssociations.size() > 0) { + doDump(fd, pw, new String[]{"associations"}, asProto); + } + doDump(fd, pw, new String[]{"processes"}, asProto); + } } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - doDump(fd, pw, args); + public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + doDump(fd, pw, args, asProto); } }; @@ -2030,7 +2045,9 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (ActivityManagerService.this) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); - if (r.thread != null) { + // Don't dispatch to isolated processes as they can't access + // ConnectivityManager and don't have network privileges anyway. + if (r.thread != null && !r.isolated) { try { r.thread.setHttpProxy(host, port, exclList, pacFileUrl); } catch (RemoteException ex) { @@ -2483,15 +2500,15 @@ public class ActivityManagerService extends IActivityManager.Stub public void setSystemProcess() { try { ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true, - DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_NORMAL); + 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_PRIORITY_HIGH | DUMP_PRIORITY_NORMAL); + DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL); ServiceManager.addService("gfxinfo", new GraphicsBinder(this)); ServiceManager.addService("dbinfo", new DbBinder(this)); if (MONITOR_CPU_USAGE) { ServiceManager.addService("cpuinfo", new CpuBinder(this), - /* allowIsolated= */ false, DUMP_PRIORITY_CRITICAL); + /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); } ServiceManager.addService("permission", new PermissionController(this)); ServiceManager.addService("processinfo", new ProcessInfoService(this)); @@ -2544,7 +2561,9 @@ public class ActivityManagerService extends IActivityManager.Stub private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { @Override - public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, + boolean asProto) { + if (asProto) return; mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, false, null); } }; @@ -2594,7 +2613,9 @@ public class ActivityManagerService extends IActivityManager.Stub private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { @Override - public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, + boolean asProto) { + if (asProto) return; if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, "cpuinfo", pw)) return; synchronized (mActivityManagerService.mProcessCpuTracker) { @@ -2764,7 +2785,7 @@ public class ActivityManagerService extends IActivityManager.Stub mTaskChangeNotificationController = new TaskChangeNotificationController(this, mStackSupervisor, mHandler); mActivityStarter = new ActivityStarter(this); - mRecentTasks = new RecentTasks(this, mStackSupervisor); + mRecentTasks = createRecentTasks(); mStackSupervisor.setRecentTasks(mRecentTasks); mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler); @@ -2810,6 +2831,14 @@ public class ActivityManagerService extends IActivityManager.Stub return new ActivityStackSupervisor(this, mHandler.getLooper()); } + protected RecentTasks createRecentTasks() { + return new RecentTasks(this, mStackSupervisor); + } + + RecentTasks getRecentTasks() { + return mRecentTasks; + } + public void setSystemServiceManager(SystemServiceManager mgr) { mSystemServiceManager = mgr; } @@ -3145,6 +3174,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { final ActivityStack stack = mStackSupervisor.getStack(stackId); if (stack == null) { + Slog.w(TAG, "setFocusedStack: No stack with id=" + stackId); return; } final ActivityRecord r = stack.topRunningActivityLocked(); @@ -3181,7 +3211,8 @@ public class ActivityManagerService extends IActivityManager.Stub /** Sets the task stack listener that gets callbacks when a task stack changes. */ @Override public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "registerTaskStackListener()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, + "registerTaskStackListener()"); mTaskChangeNotificationController.registerTaskStackListener(listener); } @@ -3190,7 +3221,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ @Override public void unregisterTaskStackListener(ITaskStackListener listener) throws RemoteException { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "unregisterTaskStackListener()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, + "unregisterTaskStackListener()"); mTaskChangeNotificationController.unregisterTaskStackListener(listener); } @@ -4868,12 +4900,9 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public final int startActivityFromRecents(int taskId, Bundle bOptions) { - if (checkCallingPermission(START_TASKS_FROM_RECENTS) != PackageManager.PERMISSION_GRANTED) { - String msg = "Permission Denial: startActivityFromRecents called without " + - START_TASKS_FROM_RECENTS; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } + enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS, + "startActivityFromRecents()"); + final long origId = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -8081,7 +8110,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Adjust the source bounds by the insets for the transition down final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint()); mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio, - true /* moveHomeStackToFront */, "enterPictureInPictureMode"); + "enterPictureInPictureMode"); final PinnedActivityStack stack = r.getStack(); stack.setPictureInPictureAspectRatio(aspectRatio); stack.setPictureInPictureActions(actions); @@ -8325,9 +8354,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - /** - * This can be called with or without the global lock held. - */ int checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported) { if (pid == MY_PID) { @@ -8402,6 +8428,15 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * This can be called with or without the global lock held. + */ + void enforceCallerIsRecentsOrHasPermission(String permission, String func) { + if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) { + enforceCallingPermission(permission, func); + } + } + + /** * Determine if UID is holding permissions required to access {@link Uri} in * the given {@link ProviderInfo}. Final permission checking is always done * in {@link ContentProvider}. @@ -9767,25 +9802,34 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public List<RunningTaskInfo> getTasks(int maxNum, int flags) { + public List<RunningTaskInfo> getTasks(int maxNum) { + return getFilteredTasks(maxNum, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED); + } + + @Override + public List<RunningTaskInfo> getFilteredTasks(int maxNum, @ActivityType int ignoreActivityType, + @WindowingMode int ignoreWindowingMode) { final int callingUid = Binder.getCallingUid(); - ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>(); + ArrayList<RunningTaskInfo> list = new ArrayList<>(); synchronized(this) { - if (DEBUG_ALL) Slog.v( - TAG, "getTasks: max=" + maxNum + ", flags=" + flags); + if (DEBUG_ALL) Slog.v(TAG, "getTasks: max=" + maxNum); final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(), callingUid); - - // TODO: Improve with MRU list from all ActivityStacks. - mStackSupervisor.getTasksLocked(maxNum, list, callingUid, allowed); + mStackSupervisor.getRunningTasks(maxNum, list, ignoreActivityType, + ignoreWindowingMode, callingUid, allowed); } return list; } private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) { + if (mRecentTasks.isCallerRecents(callingUid)) { + // Always allow the recents component to get tasks + return true; + } + boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; if (!allowed) { @@ -9833,8 +9877,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public ActivityManager.TaskDescription getTaskDescription(int id) { synchronized (this) { - enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, - "getTaskDescription()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getTaskDescription()"); final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS); if (tr != null) { @@ -10028,7 +10071,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void cancelTaskWindowTransition(int taskId) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskWindowTransition()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, + "cancelTaskWindowTransition()"); final long ident = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -10047,7 +10091,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void cancelTaskThumbnailTransition(int taskId) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskThumbnailTransition()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, + "cancelTaskThumbnailTransition()"); final long ident = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -10066,7 +10111,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) { - enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()"); + enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()"); final long ident = Binder.clearCallingIdentity(); try { final TaskRecord task; @@ -10119,12 +10164,13 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void removeStack(int stackId) { - enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()"); synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { final ActivityStack stack = mStackSupervisor.getStack(stackId); if (stack == null) { + Slog.w(TAG, "removeStack: No stack with id=" + stackId); return; } if (!stack.isActivityTypeStandardOrUndefined()) { @@ -10144,7 +10190,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ @Override public void removeStacksInWindowingModes(int[] windowingModes) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksInWindowingModes()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, + "removeStacksInWindowingModes()"); synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { @@ -10157,7 +10204,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void removeStacksWithActivityTypes(int[] activityTypes) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksWithActivityTypes()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, + "removeStacksWithActivityTypes()"); synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { @@ -10186,7 +10234,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public boolean removeTask(int taskId) { - enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()"); + enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeTask()"); synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { @@ -10229,11 +10277,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode"); return; } - final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked(); - if (prev != null) { - task.setTaskToReturnTo(prev); - } - mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options, "moveTaskToFront", + mStackSupervisor.findTaskToMoveToFront(task, flags, options, "moveTaskToFront", false /* forceNonResizable */); final ActivityRecord topActivity = task.getTopActivity(); @@ -10318,7 +10362,8 @@ public class ActivityManagerService extends IActivityManager.Stub } // TODO(multi-display): Have the caller pass in the windowing mode and activity type. final ActivityStack stack = display.createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/); + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + ON_TOP); return (stack != null) ? stack.mStackId : INVALID_STACK_ID; } } @@ -10366,7 +10411,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()"); synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { @@ -10402,7 +10447,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void moveTaskToStack(int taskId, int stackId, boolean toTop) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()"); synchronized (this) { long ident = Binder.clearCallingIdentity(); try { @@ -10453,7 +10498,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate, Rect initialBounds) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()"); synchronized (this) { long ident = Binder.clearCallingIdentity(); try { @@ -10492,12 +10537,16 @@ public class ActivityManagerService extends IActivityManager.Stub */ @Override public void dismissSplitScreenMode(boolean toTop) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()"); final long ident = Binder.clearCallingIdentity(); try { synchronized (this) { final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack(); + if (stack == null) { + Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found."); + return; + } if (toTop) { mStackSupervisor.resizeStackLocked(stack, null /* destBounds */, null /* tempTaskBounds */, null /* tempTaskInsetBounds */, @@ -10520,14 +10569,14 @@ public class ActivityManagerService extends IActivityManager.Stub */ @Override public void dismissPip(boolean animate, int animationDuration) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()"); final long ident = Binder.clearCallingIdentity(); try { synchronized (this) { final PinnedActivityStack stack = mStackSupervisor.getDefaultDisplay().getPinnedStack(); - if (stack == null) { + Slog.w(TAG, "dismissPip: pinned stack not found."); return; } if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) { @@ -10557,7 +10606,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ @Override public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTopActivityToPinnedStack()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, + "moveTopActivityToPinnedStack()"); synchronized (this) { if (!mSupportsPictureInPicture) { throw new IllegalStateException("moveTopActivityToPinnedStack:" @@ -10576,13 +10626,14 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode, boolean preserveWindows, boolean animate, int animationDuration) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()"); long ident = Binder.clearCallingIdentity(); try { synchronized (this) { if (animate) { final PinnedActivityStack stack = mStackSupervisor.getStack(stackId); if (stack == null) { + Slog.w(TAG, "resizeStack: stackId " + stackId + " not found."); return; } if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) { @@ -10611,8 +10662,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds, Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) { - enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, - "resizeDockedStack()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeDockedStack()"); long ident = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -10627,8 +10677,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void resizePinnedStack(Rect pinnedBounds, Rect tempPinnedTaskBounds) { - enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, - "resizePinnedStack()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePinnedStack()"); final long ident = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -10687,7 +10736,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public List<StackInfo> getAllStackInfos() { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()"); long ident = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -10700,7 +10749,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public StackInfo getStackInfo(int windowingMode, int activityType) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()"); long ident = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -10743,6 +10792,20 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @Override + public void updateLockTaskFeatures(int userId, int flags) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != SYSTEM_UID) { + enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES, + "updateLockTaskFeatures()"); + } + synchronized (this) { + if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowing features " + userId + ":0x" + + Integer.toHexString(flags)); + mLockTaskController.updateLockTaskFeatures(userId, flags); + } + } + private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isAppPinning) { if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task); if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { @@ -11861,8 +11924,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void appNotRespondingViaProvider(IBinder connection) { - enforceCallingPermission( - android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()"); + enforceCallingPermission(REMOVE_TASKS, "appNotRespondingViaProvider()"); final ContentProviderConnection conn = (ContentProviderConnection) connection; if (conn == null) { @@ -12330,6 +12392,9 @@ public class ActivityManagerService extends IActivityManager.Stub + android.Manifest.permission.SHUTDOWN); } + // TODO: Where should the corresponding '1' (start) write go? + StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, 0); + boolean timedout = false; synchronized(this) { @@ -13512,6 +13577,7 @@ public class ActivityManagerService extends IActivityManager.Stub stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid, sourcePkg != null ? sourcePkg : rec.key.packageName); pkg.noteWakeupAlarmLocked(tag); + StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid); } } } @@ -13892,6 +13958,9 @@ public class ActivityManagerService extends IActivityManager.Stub com.android.internal.R.string.config_appsNotReportingCrashes)); mUserController.mUserSwitchUiEnabled = !res.getBoolean( com.android.internal.R.bool.config_customUserSwitchUi); + mUserController.mMaxRunningUsers = res.getInteger( + com.android.internal.R.integer.config_multiuserMaxRunningUsers); + if ((globalConfig.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) { mFullscreenThumbnailScale = (float) res .getInteger(com.android.internal.R.integer.thumbnail_width_tv) / @@ -14083,6 +14152,23 @@ public class ActivityManagerService extends IActivityManager.Stub } mStackSupervisor.resumeFocusedStackTopActivityLocked(); mUserController.sendUserSwitchBroadcasts(-1, currentUserId); + + BinderInternal.nSetBinderProxyCountEnabled(true); + BinderInternal.setBinderProxyCountCallback( + new BinderInternal.BinderProxyLimitListener() { + @Override + public void onLimitReached(int uid) { + Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid " + + Process.myUid()); + if (uid == Process.SYSTEM_UID) { + Slog.i(TAG, "Skipping kill (uid is SYSTEM)"); + } else { + killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid), + "Too many Binders sent to SYSTEM"); + } + } + }, mHandler); + traceLog.traceEnd(); // ActivityManagerStartApps traceLog.traceEnd(); // PhaseActivityManagerReady } @@ -14239,12 +14325,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } sb.append("\n"); - if (info.crashInfo != null && info.crashInfo.stackTrace != null) { - sb.append(info.crashInfo.stackTrace); + if (info.hasStackTrace()) { + sb.append(info.getStackTrace()); sb.append("\n"); } - if (info.message != null) { - sb.append(info.message); + if (info.getViolationDetails() != null) { + sb.append(info.getViolationDetails()); sb.append("\n"); } @@ -14789,7 +14875,7 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Wrapper function to print out debug data filtered by specified arguments. */ - private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) { + private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; boolean dumpAll = false; @@ -14798,7 +14884,6 @@ public class ActivityManagerService extends IActivityManager.Stub boolean dumpCheckinFormat = false; boolean dumpVisibleStacksOnly = false; boolean dumpFocusedStackOnly = false; - boolean useProto = false; String dumpPackage = null; int opti = 0; @@ -14832,8 +14917,6 @@ public class ActivityManagerService extends IActivityManager.Stub } else if ("-h".equals(opt)) { ActivityManagerShellCommand.dumpHelp(pw, true); return; - } else if ("--proto".equals(opt)) { - useProto = true; } else { pw.println("Unknown argument: " + opt + "; use -h for help"); } @@ -14900,6 +14983,19 @@ public class ActivityManagerService extends IActivityManager.Stub mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage); } } + } else if ("binder-proxies".equals(cmd)) { + if (opti >= args.length) { + dumpBinderProxiesCounts(pw, BinderInternal.nGetBinderProxyPerUidCounts(), + "Counts of Binder Proxies held by SYSTEM"); + } else { + String uid = args[opti]; + opti++; + // Ensure Binder Proxy Count is as up to date as possible + System.gc(); + System.runFinalization(); + System.gc(); + pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid))); + } } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) { String[] newArgs; String name; @@ -15418,6 +15514,34 @@ public class ActivityManagerService extends IActivityManager.Stub return printed; } + boolean dumpBinderProxiesCounts(PrintWriter pw, SparseIntArray counts, String header) { + if(counts != null) { + pw.println(header); + for (int i = 0; i < counts.size(); i++) { + final int uid = counts.keyAt(i); + final int binderCount = counts.valueAt(i); + pw.print(" UID "); + pw.print(uid); + pw.print(", binder count = "); + pw.print(binderCount); + pw.print(", package(s)= "); + final String[] pkgNames = mContext.getPackageManager().getPackagesForUid(uid); + if (pkgNames != null) { + for (int j = 0; j < pkgNames.length; j++) { + pw.print(pkgNames[j]); + pw.print("; "); + } + } else { + pw.print("NO PACKAGE NAME FOUND"); + } + pw.println(); + } + pw.println(); + return true; + } + return false; + } + void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; @@ -15623,7 +15747,8 @@ public class ActivityManagerService extends IActivityManager.Stub } pw.println(" mPreviousProcess: " + mPreviousProcess); } - if (dumpAll) { + if (dumpAll && (mPreviousProcess == null || dumpPackage == null + || mPreviousProcess.pkgList.containsKey(dumpPackage))) { StringBuilder sb = new StringBuilder(128); sb.append(" mPreviousProcessVisibleTime: "); TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb); @@ -15642,7 +15767,9 @@ public class ActivityManagerService extends IActivityManager.Stub mStackSupervisor.dumpDisplayConfigs(pw, " "); } if (dumpAll) { - pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange); + if (dumpPackage == null) { + pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange); + } if (mCompatModePackages.getPackages().size() > 0) { boolean printed = false; for (Map.Entry<String, Integer> entry @@ -15722,8 +15849,8 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" mRunningVoice=" + mRunningVoice); pw.println(" mVoiceWakeLock" + mVoiceWakeLock); } + pw.println(" mVrController=" + mVrController); } - pw.println(" mVrController=" + mVrController); if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { if (dumpPackage == null || dumpPackage.equals(mDebugApp) @@ -18060,8 +18187,7 @@ public class ActivityManagerService extends IActivityManager.Stub // ========================================================= @Override - public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, - int flags) { + public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags) { enforceNotIsolatedCaller("getServices"); final int callingUid = Binder.getCallingUid(); @@ -20005,7 +20131,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public StackInfo getFocusedStackInfo() throws RemoteException { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()"); long ident = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -20045,7 +20171,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override // TODO: API should just be about changing windowing modes... public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) { - enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()"); + enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, + "moveTasksToFullscreenStack()"); synchronized (this) { final long origId = Binder.clearCallingIdentity(); try { diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java index f03d2d53..f9422656 100644 --- a/com/android/server/am/ActivityManagerShellCommand.java +++ b/com/android/server/am/ActivityManagerShellCommand.java @@ -222,6 +222,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSetInactive(pw); case "get-inactive": return runGetInactive(pw); + case "set-standby-bucket": + return runSetStandbyBucket(pw); case "send-trim-memory": return runSendTrimMemory(pw); case "display": @@ -1824,6 +1826,27 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runSetStandbyBucket(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + String packageName = getNextArgRequired(); + String value = getNextArgRequired(); + + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + usm.setAppStandbyBucket(packageName, Integer.parseInt(value), userId); + return 0; + } + int runGetInactive(PrintWriter pw) throws RemoteException { int userId = UserHandle.USER_CURRENT; @@ -2282,7 +2305,7 @@ final class ActivityManagerShellCommand extends ShellCommand { int runWrite(PrintWriter pw) { mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, "registerUidObserver()"); - mInternal.mRecentTasks.flush(); + mInternal.getRecentTasks().flush(); pw.println("All tasks persisted."); return 0; } @@ -2571,6 +2594,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Sets the inactive state of an app."); pw.println(" get-inactive [--user <USER_ID>] <PACKAGE>"); pw.println(" Returns the inactive state of an app."); + pw.println(" set-standby-bucket [--user <USER_ID>] <PACKAGE> <BUCKET>"); + pw.println(" Puts an app in the standby bucket."); pw.println(" send-trim-memory [--user <USER_ID>] <PROCESS>"); pw.println(" [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]"); pw.println(" Send a memory trim event to a <PROCESS>. May also supply a raw trim int level."); diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java index 2c72a4db..2f0b6491 100644 --- a/com/android/server/am/ActivityRecord.java +++ b/com/android/server/am/ActivityRecord.java @@ -165,6 +165,7 @@ import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IApplicationToken; 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; @@ -203,7 +204,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY; private static final boolean SHOW_ACTIVITY_START_TIME = true; - private static final String RECENTS_PACKAGE_NAME = "com.android.systemui.recents"; private static final String ATTR_ID = "id"; private static final String TAG_INTENT = "intent"; @@ -232,7 +232,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo final String processName; // process where this component wants to run final String taskAffinity; // as per ActivityInfo.taskAffinity final boolean stateNotNeeded; // As per ActivityInfo.flags - boolean fullscreen; // covers the full screen? + boolean fullscreen; // The activity is opaque and fills the entire space of this task. + // TODO: See if it possible to combine this with the fullscreen field. + final boolean hasWallpaper; // Has a wallpaper window as a background. final boolean noDisplay; // activity is not displayed? private final boolean componentSpecified; // did caller specify an explicit component? final boolean rootVoiceInteraction; // was this the root activity of a voice interaction? @@ -274,6 +276,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo ActivityState state; // current state we are in Bundle icicle; // last saved activity state PersistableBundle persistentState; // last persistently saved activity state + // TODO: See if this is still needed. boolean frontOfTask; // is this the root activity of its task? boolean launchFailed; // set if a launched failed, to abort on 2nd try boolean haveState; // have we gotten the last activity state? @@ -883,9 +886,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo Entry ent = AttributeCache.instance().get(packageName, realTheme, com.android.internal.R.styleable.Window, userId); - fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array); - noDisplay = ent != null && ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowNoDisplay, false); + if (ent != null) { + fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array); + hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false); + noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); + } else { + hasWallpaper = false; + noDisplay = false; + } setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord); @@ -1049,7 +1057,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // We only allow home activities to be resizeable if they explicitly requested it. info.resizeMode = RESIZE_MODE_UNRESIZEABLE; } - } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) { + } else if (service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) { activityType = ACTIVITY_TYPE_RECENTS; } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT && canLaunchAssistActivity(launchedFromPackage)) { @@ -1553,17 +1561,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return false; } - boolean isVisible = !behindFullscreenActivity || mLaunchTaskBehind; - - if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) { - // On devices that support leanback only (Android TV), Recents activity can only be - // visible if the home stack is the focused stack or we are in split-screen mode. - final ActivityDisplay display = getDisplay(); - boolean hasSplitScreenStack = display != null && display.hasSplitScreenPrimaryStack(); - isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack()); - } - - return isVisible; + return !behindFullscreenActivity || mLaunchTaskBehind; } void makeVisibleIfNeeded(ActivityRecord starting) { @@ -2065,7 +2063,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo final File iconFile = new File(TaskPersister.getUserImagesDir(task.userId), iconFilename); final String iconFilePath = iconFile.getAbsolutePath(); - service.mRecentTasks.saveImage(icon, iconFilePath); + service.getRecentTasks().saveImage(icon, iconFilePath); _taskDescription.setIconFilename(iconFilePath); } taskDescription = _taskDescription; @@ -2812,7 +2810,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - super.writeToProto(proto, CONFIGURATION_CONTAINER); + super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); writeIdentifierToProto(proto, IDENTIFIER); proto.write(STATE, state.toString()); proto.write(VISIBLE, visible); diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java index 941c371b..ba41bd4c 100644 --- a/com/android/server/am/ActivityStack.java +++ b/com/android/server/am/ActivityStack.java @@ -16,13 +16,10 @@ package com.android.server.am; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 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; @@ -102,12 +99,13 @@ import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IActivityController; import android.app.ResultInfo; +import android.app.WindowConfiguration.ActivityType; +import android.app.WindowConfiguration.WindowingMode; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; import android.os.Binder; @@ -346,7 +344,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private final SparseArray<Rect> mTmpBounds = new SparseArray<>(); private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>(); private final Rect mTmpRect2 = new Rect(); - private final Point mTmpSize = new Point(); /** Run all ActivityStacks through this */ protected final ActivityStackSupervisor mStackSupervisor; @@ -489,26 +486,23 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai activityType = ACTIVITY_TYPE_STANDARD; } final ActivityDisplay display = getDisplay(); - if (display != null) { - if (activityType == ACTIVITY_TYPE_STANDARD + if (display != null && activityType == ACTIVITY_TYPE_STANDARD && windowingMode == WINDOWING_MODE_UNDEFINED) { - // Standard activity types will mostly take on the windowing mode of the display if - // one isn't specified, so look-up a compatible stack based on the display's - // windowing mode. - windowingMode = display.getWindowingMode(); - } - windowingMode = - display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType); + // Standard activity types will mostly take on the windowing mode of the display if one + // isn't specified, so look-up a compatible stack based on the display's windowing mode. + windowingMode = display.getWindowingMode(); } return super.isCompatible(windowingMode, activityType); } /** Adds the stack to specified display and calls WindowManager to do the same. */ void reparent(ActivityDisplay activityDisplay, boolean onTop) { + // TODO: We should probably resolve the windowing mode for the stack on the new display here + // so that it end up in a compatible mode in the new display. e.g. split-screen secondary. removeFromDisplay(); mTmpRect2.setEmpty(); postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop); - adjustFocusToNextFocusableStackLocked("reparent", true /* allowFocusSelf */); + adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */); mStackSupervisor.resumeFocusedStackTopActivityLocked(); // Update visibility of activities before notifying WM. This way it won't try to resize // windows that are no longer visible. @@ -837,6 +831,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return mDisplayId == DEFAULT_DISPLAY; } + private boolean returnsToHomeStack() { + return !inMultiWindowMode() + && !mTaskHistory.isEmpty() + && mTaskHistory.get(0).returnsToHomeStack(); + } + void moveToFront(String reason) { moveToFront(reason, null); } @@ -850,6 +850,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return; } + if (!isActivityTypeHome() && returnsToHomeStack()) { + // Make sure the home stack is behind this stack since that is where we should return to + // when this stack is no longer visible. + mStackSupervisor.moveHomeStackToFront(reason + " returnToHome"); + } + getDisplay().positionChildAtTop(this); mStackSupervisor.setFocusStackUnchecked(reason, this); if (task != null) { @@ -1496,25 +1502,17 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - /** Returns true if the stack contains a fullscreen task. */ - private boolean hasFullscreenTask() { - for (int i = mTaskHistory.size() - 1; i >= 0; --i) { - final TaskRecord task = mTaskHistory.get(i); - if (task.mFullscreen) { - return true; - } - } - return false; - } - /** * Returns true if the stack is translucent and can have other contents visible behind it if * needed. A stack is considered translucent if it don't contain a visible or * starting (about to be visible) activity that is fullscreen (opaque). * @param starting The currently starting activity or null if there is none. - * @param stackBehind The stack directly behind this one. */ - private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) { + @VisibleForTesting + boolean isStackTranslucent(ActivityRecord starting) { + if (!isAttached() || mForceHidden) { + return true; + } for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); final ArrayList<ActivityRecord> activities = task.mActivities; @@ -1533,21 +1531,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai continue; } - if (r.fullscreen) { + if (r.fullscreen || r.hasWallpaper) { // Stack isn't translucent if it has at least one fullscreen activity // that is visible. return false; } - - final boolean stackBehindHomeOrRecent = stackBehind != null - && stackBehind.isHomeOrRecentsStack(); - if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack() - && !stackBehindHomeOrRecent && !isActivityTypeAssistant()) { - // Stack isn't translucent if it's top activity should have the home stack - // behind it and the stack currently behind it isn't the home or recents stack - // or the assistant stack. - return false; - } } } return true; @@ -1572,129 +1560,68 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (!isAttached() || mForceHidden) { return false; } - - final ActivityDisplay display = getDisplay(); - if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) { - return true; - } - - final int stackIndex = display.getIndexOf(this); - - // Check position and visibility of this stack relative to the front stack on its display. - final ActivityStack topStack = getDisplay().getTopStack(); - final int windowingMode = getWindowingMode(); - final int activityType = getActivityType(); - - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // If the assistant stack is focused and translucent, then the docked stack is always - // visible - if (topStack.isActivityTypeAssistant()) { - return topStack.isStackTranslucent(starting, this); - } + if (mStackSupervisor.isFocusedStack(this)) { return true; } - // Set home stack to invisible when it is below but not immediately below the docked stack - // A case would be if recents stack exists but has no tasks and is below the docked stack - // and home stack is below recents - if (activityType == ACTIVITY_TYPE_HOME) { - final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack(); - int dockedStackIndex = display.getIndexOf(splitScreenStack); - if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) { - return false; - } - } - - // Find the first stack behind front stack that actually got something visible. - int stackBehindTopIndex = display.getIndexOf(topStack) - 1; - while (stackBehindTopIndex >= 0 && - display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) { - stackBehindTopIndex--; - } - final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0) - ? display.getChildAt(stackBehindTopIndex) : null; - int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED; - int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED; - if (stackBehindTop != null) { - stackBehindTopWindowingMode = stackBehindTop.getWindowingMode(); - stackBehindTopActivityType = stackBehindTop.getActivityType(); + final ActivityRecord top = topRunningActivityLocked(); + if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) { + // Shouldn't be visible if you don't have any running activities, not starting one, and + // not the top stack on display. + return false; } - final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop(); - if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) { - if (this == stackBehindTop) { - // Stacks directly behind the docked or pinned stack are always visible. + final ActivityDisplay display = getDisplay(); + boolean gotOpaqueSplitScreenPrimary = false; + boolean gotOpaqueSplitScreenSecondary = false; + final int windowingMode = getWindowingMode(); + for (int i = display.getChildCount() - 1; i >= 0; --i) { + final ActivityStack other = display.getChildAt(i); + if (other == this) { + // Should be visible if there is no other stack occluding it. return true; - } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) { - // Otherwise, this stack can also be visible if it is directly behind a docked stack - // or translucent assistant stack behind an always-on-top top-most stack - if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - return true; - } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) { - return stackBehindTop.isStackTranslucent(starting, this); - } } - } - if (topStack.isBackdropToTranslucentActivity() - && topStack.isStackTranslucent(starting, stackBehindTop)) { - // Stacks behind the fullscreen or assistant stack with a translucent activity are - // always visible so they can act as a backdrop to the translucent activity. - // For example, dialog activities - if (stackIndex == stackBehindTopIndex) { - return true; - } - if (stackBehindTopIndex >= 0) { - if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED) - && stackIndex == (stackBehindTopIndex - 1)) { - // The stack behind the docked or pinned stack is also visible so we can have a - // complete backdrop to the translucent activity when the docked stack is up. - return true; - } - } - } - - if (isOnHomeDisplay()) { - // Visibility of any stack on default display should have been determined by the - // conditions above. - return false; - } + final int otherWindowingMode = other.getWindowingMode(); + // TODO: Can be removed once we are no longer using returnToType for back functionality + final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null; - final int stackCount = display.getChildCount(); - for (int i = stackIndex + 1; i < stackCount; i++) { - final ActivityStack stack = display.getChildAt(i); - - if (!stack.mFullscreen && !stack.hasFullscreenTask()) { - continue; - } - - if (!stack.isDynamicStacksVisibleBehindAllowed()) { - // These stacks can't have any dynamic stacks visible behind them. + if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) { + if (other.isStackTranslucent(starting)) { + // Can be visible behind a translucent fullscreen stack. + continue; + } return false; + } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + && !gotOpaqueSplitScreenPrimary) { + gotOpaqueSplitScreenPrimary = + !other.isStackTranslucent(starting); + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + && gotOpaqueSplitScreenPrimary) { + // Can not be visible behind another opaque stack in split-screen-primary mode. + return false; + } + } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && !gotOpaqueSplitScreenSecondary) { + gotOpaqueSplitScreenSecondary = + !other.isStackTranslucent(starting); + if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && gotOpaqueSplitScreenSecondary) { + // Can not be visible behind another opaque stack in split-screen-secondary mode. + return false; + } } - - if (!stack.isStackTranslucent(starting, null /* stackBehind */)) { + if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) { + // Can not be visible if we are in split-screen windowing mode and both halves of + // the screen are opaque. return false; } } + // Well, nothing is stopping you from being visible... return true; } - private boolean isBackdropToTranslucentActivity() { - if (isActivityTypeAssistant()) { - return true; - } - final int windowingMode = getWindowingMode(); - return windowingMode == WINDOWING_MODE_FULLSCREEN - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - } - - private boolean isDynamicStacksVisibleBehindAllowed() { - return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED; - } - final int rankTaskLayers(int baseLayer) { int layer = 0; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { @@ -1713,6 +1640,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * Make sure that all activities that need to be visible (that is, they * currently can be seen by the user) actually are. */ + // TODO: Should be re-worked based on the fact that each task as a stack in most cases. final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges, boolean preserveWindows) { mTopActivityOccludesKeyguard = false; @@ -1757,7 +1685,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai visibleIgnoringKeyguard, isTop); if (visibleIgnoringKeyguard) { behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible, - behindFullscreenActivity, task, r); + behindFullscreenActivity, r); } if (reallyVisible) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r @@ -1816,16 +1744,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // show activities in the next application stack behind them vs. another // task in the home stack like recents. behindFullscreenActivity = true; - } else if (windowingMode == WINDOWING_MODE_FULLSCREEN - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task - + " returning to non-application type=" + task.getTaskToReturnTo()); - // Once we reach a fullscreen stack task that has a running activity and should - // return to another stack task, then no other activities behind that one should - // be visible. - if (task.topRunningActivityLocked() != null && !task.returnsToStandardTask()) { - behindFullscreenActivity = true; - } } } @@ -1861,24 +1779,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } /** - * Returns true if we try to maintain focus in the current stack when the top activity finishes. - */ - private boolean keepFocusInStackIfPossible() { - final int windowingMode = getWindowingMode(); - return windowingMode == WINDOWING_MODE_FREEFORM - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || windowingMode == WINDOWING_MODE_PINNED; - } - - /** - * Returns true if the top task in the task is allowed to return home when finished and - * there are other tasks in the stack. - */ - boolean allowTopTaskToReturnHome() { - return !inPinnedWindowingMode(); - } - - /** * @return the top most visible activity that wants to dismiss Keyguard */ ActivityRecord getTopDismissingKeyguardActivity() { @@ -2039,18 +1939,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity, - TaskRecord task, ActivityRecord r) { + ActivityRecord r) { if (r.fullscreen) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r + " stackInvisible=" + stackInvisible + " behindFullscreenActivity=" + behindFullscreenActivity); // At this point, nothing else needs to be shown in this task. behindFullscreenActivity = true; - } else if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r - + " stackInvisible=" + stackInvisible - + " behindFullscreenActivity=" + behindFullscreenActivity); - behindFullscreenActivity = true; } return behindFullscreenActivity; } @@ -2209,7 +2104,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final boolean hasRunningActivity = next != null; // TODO: Maybe this entire condition can get removed? - if (hasRunningActivity && getDisplay() == null) { + if (hasRunningActivity && !isAttached()) { return false; } @@ -2239,28 +2134,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - final TaskRecord nextTask = next.getTask(); - final TaskRecord prevTask = prev != null ? prev.getTask() : null; - if (prevTask != null && prevTask.getStack() == this && - prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) { - if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); - if (prevTask == nextTask) { - prevTask.setFrontOfTask(); - } else if (prevTask != topTask()) { - // This task is going away but it was supposed to return to the home stack. - // Now the task above it has to return to the home task instead. - final int taskNdx = mTaskHistory.indexOf(prevTask) + 1; - mTaskHistory.get(taskNdx).setTaskToReturnTo(ACTIVITY_TYPE_HOME); - } else if (!isOnHomeDisplay()) { - return false; - } else if (!isActivityTypeHome()){ - if (DEBUG_STATES) Slog.d(TAG_STATES, - "resumeTopActivityLocked: Launching home next"); - return isOnHomeDisplay() && - mStackSupervisor.resumeHomeStackTask(prev, "prevFinished"); - } - } - // If we are sleeping, and there is no resumed activity, and the top // activity is paused, well that is the state we want. if (shouldSleepOrShutDownActivities() @@ -2644,7 +2517,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev, ActivityOptions options, String reason) { - if (adjustFocusToNextFocusableStackLocked(reason)) { + if (adjustFocusToNextFocusableStack(reason)) { // Try to move focus to the next visible stack with a running activity if this // stack is not covering the entire screen or is on a secondary display (with no home // stack). @@ -2718,7 +2591,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } private void insertTaskAtTop(TaskRecord task, ActivityRecord starting) { - updateTaskReturnToForTopInsertion(task); // TODO: Better place to put all the code below...may be addTask... mTaskHistory.remove(task); // Now put task at top. @@ -2729,57 +2601,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai true /* includingParents */); } - /** - * Updates the {@param task}'s return type before it is moved to the top. - */ - private void updateTaskReturnToForTopInsertion(TaskRecord task) { - boolean isLastTaskOverHome = false; - // If the moving task is over the home or assistant stack, transfer its return type to next - // task so that they return to the same stack - if (task.isOverHomeStack() || task.isOverAssistantStack()) { - final TaskRecord nextTask = getNextTask(task); - if (nextTask != null) { - nextTask.setTaskToReturnTo(task.getTaskToReturnTo()); - } else { - isLastTaskOverHome = true; - } - } - - // If this is not on the default display, then just set the return type to application - if (!isOnHomeDisplay()) { - task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); - return; - } - - final ActivityStack lastStack = mStackSupervisor.getLastStack(); - - // If there is no last task, do not set task to return to - if (lastStack == null) { - return; - } - - // If the task was launched from the assistant stack, set the return type to assistant - if (lastStack.isActivityTypeAssistant()) { - task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); - return; - } - - // If this is being moved to the top by another activity or being launched from the home - // activity, set mTaskToReturnTo accordingly. - final boolean fromHomeOrRecents = lastStack.isHomeOrRecentsStack(); - final TaskRecord topTask = lastStack.topTask(); - if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) { - // If it's a last task over home - we default to keep its return to type not to - // make underlying task focused when this one will be finished. - int returnToType = isLastTaskOverHome - ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD; - if (fromHomeOrRecents && allowTopTaskToReturnHome()) { - returnToType = topTask == null ? ACTIVITY_TYPE_HOME : topTask.getActivityType(); - } - task.setTaskToReturnTo(returnToType); - } - } - final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity, boolean newTask, boolean keepCurTransition, ActivityOptions options) { TaskRecord rTask = r.getTask(); @@ -3049,7 +2870,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } mWindowContainerController.positionChildAtBottom( - targetTask.getWindowContainerController()); + targetTask.getWindowContainerController(), false /* includingParents */); replyChainEnd = -1; } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) { // If the activity should just be removed -- either @@ -3285,7 +3106,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } /** Returns true if the task is one of the task finishing on-top of the top running task. */ - boolean isATopFinishingTask(TaskRecord task) { + private boolean isATopFinishingTask(TaskRecord task) { for (int i = mTaskHistory.size() - 1; i >= 0; --i) { final TaskRecord current = mTaskHistory.get(i); final ActivityRecord r = current.topRunningActivityLocked(); @@ -3300,7 +3121,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - private void adjustFocusedActivityStackLocked(ActivityRecord r, String reason) { + private void adjustFocusedActivityStack(ActivityRecord r, String reason) { if (!mStackSupervisor.isFocusedStack(this) || ((mResumedActivity != r) && (mResumedActivity != null))) { return; @@ -3309,66 +3130,44 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final ActivityRecord next = topRunningActivityLocked(); final String myReason = reason + " adjustFocus"; - if (next != r) { - if (next != null && keepFocusInStackIfPossible() && isFocusable()) { - // For freeform, docked, and pinned stacks we always keep the focus within the - // stack as long as there is a running activity. - return; - } else { - // Task is not guaranteed to be non-null. For example, destroying the - // {@link ActivityRecord} will disassociate the task from the activity. - final TaskRecord task = r.getTask(); + if (next == r) { + mStackSupervisor.moveFocusableActivityStackToFrontLocked( + mStackSupervisor.topRunningActivityLocked(), myReason); + return; + } - if (task == null) { - throw new IllegalStateException("activity no longer associated with task:" + r); - } + if (next != null && isFocusable()) { + // Keep focus in stack if we have a top running activity and are focusable. + return; + } - final boolean isAssistantOrOverAssistant = - task.getStack().isActivityTypeAssistant() || task.isOverAssistantStack(); - if (r.frontOfTask && isATopFinishingTask(task) - && (task.isOverHomeStack() || isAssistantOrOverAssistant)) { - // For non-fullscreen or assistant stack, we want to move the focus to the next - // visible stack to prevent the home screen from moving to the top and obscuring - // other visible stacks. - if ((!mFullscreen || isAssistantOrOverAssistant) - && adjustFocusToNextFocusableStackLocked(myReason)) { - return; - } - // Move the home stack to the top if this stack is fullscreen or there is no - // other visible stack. - if (task.isOverHomeStack() && - mStackSupervisor.moveHomeStackTaskToTop(myReason)) { - // Activity focus was already adjusted. Nothing else to do... - return; - } - } - } + // Task is not guaranteed to be non-null. For example, destroying the + // {@link ActivityRecord} will disassociate the task from the activity. + final TaskRecord task = r.getTask(); + + if (task == null) { + throw new IllegalStateException("activity no longer associated with task:" + r); } - mStackSupervisor.moveFocusableActivityStackToFrontLocked( - mStackSupervisor.topRunningActivityLocked(), myReason); + // Move focus to next focusable stack if possible. + if (adjustFocusToNextFocusableStack(myReason)) { + return; + } + + // Whatever...go home. + mStackSupervisor.moveHomeStackTaskToTop(myReason); } /** Find next proper focusable stack and make it focused. */ - private boolean adjustFocusToNextFocusableStackLocked(String reason) { - return adjustFocusToNextFocusableStackLocked(reason, false /* allowFocusSelf */); + private boolean adjustFocusToNextFocusableStack(String reason) { + return adjustFocusToNextFocusableStack(reason, false /* allowFocusSelf */); } /** * Find next proper focusable stack and make it focused. * @param allowFocusSelf Is the focus allowed to remain on the same stack. */ - private boolean adjustFocusToNextFocusableStackLocked(String reason, boolean allowFocusSelf) { - if (isActivityTypeAssistant() && bottomTask() != null - && bottomTask().returnsToHomeTask()) { - // If the current stack is the assistant stack, then use the return-to type to determine - // whether to return to the home screen. This is needed to workaround an issue where - // launching a fullscreen task (and subequently returning from that task) will cause - // the fullscreen stack to be found as the next focusable stack below, even if the - // assistant was launched over home. - return mStackSupervisor.moveHomeStackTaskToTop(reason); - } - + private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) { final ActivityStack stack = mStackSupervisor.getNextFocusableStackLocked( allowFocusSelf ? null : this); final String myReason = reason + " adjustFocusToNextFocusableStack"; @@ -3378,22 +3177,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final ActivityRecord top = stack.topRunningActivityLocked(); - if (stack.isHomeOrRecentsStack() && (top == null || !top.visible)) { + if (stack.isActivityTypeHome() && (top == null || !top.visible)) { // If we will be focusing on the home stack next and its current top activity isn't - // visible, then use the task return to value to determine the home task to display - // next. + // visible, then use the move the home stack task to top to make the activity visible. return mStackSupervisor.moveHomeStackTaskToTop(reason); } - if (stack.isActivityTypeAssistant() && top != null - && top.getTask().returnsToHomeTask()) { - // It is possible for the home stack to not be directly underneath the assistant stack. - // For example, the assistant may start an activity in the fullscreen stack. Upon - // returning to the assistant stack, we must ensure that the home stack is underneath - // when appropriate. - mStackSupervisor.moveHomeStackTaskToTop("adjustAssistantReturnToHome"); - } - stack.moveToFront(myReason); return true; } @@ -3408,7 +3197,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, "stop-no-history", false)) { // If {@link requestFinishActivityLocked} returns {@code true}, - // {@link adjustFocusedActivityStackLocked} would have been already called. + // {@link adjustFocusedActivityStack} would have been already called. r.resumeKeyDispatchingLocked(); return; } @@ -3420,7 +3209,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } if (r.app != null && r.app.thread != null) { - adjustFocusedActivityStackLocked(r, "stopActivity"); + adjustFocusedActivityStack(r, "stopActivity"); r.resumeKeyDispatchingLocked(); try { r.stopped = false; @@ -3659,7 +3448,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai r.pauseKeyDispatchingLocked(); - adjustFocusedActivityStackLocked(r, "finishActivity"); + adjustFocusedActivityStack(r, "finishActivity"); finishActivityResultsLocked(r, resultCode, resultData); @@ -3826,7 +3615,21 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - final boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) { + /** @return true if the stack behind this one is a standard activity type. */ + boolean inFrontOfStandardStack() { + final ActivityDisplay display = getDisplay(); + if (display == null) { + return false; + } + final int index = display.getIndexOf(this); + if (index == 0) { + return false; + } + final ActivityStack stackBehind = display.getChildAt(index - 1); + return stackBehind.isActivityTypeStandard(); + } + + boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) { // Basic case: for simple app-centric recents, we need to recreate // the task if the affinity has changed. if (srec == null || srec.getTask().affinity == null || @@ -3838,10 +3641,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // of a document, unless simply finishing it will return them to the the // correct app behind. final TaskRecord task = srec.getTask(); - if (srec.frontOfTask && task != null && task.getBaseIntent() != null - && task.getBaseIntent().isDocument()) { + if (srec.frontOfTask && task.getBaseIntent() != null && task.getBaseIntent().isDocument()) { // Okay, this activity is at the root of its task. What to do, what to do... - if (!task.returnsToStandardTask()) { + if (!inFrontOfStandardStack()) { // Finishing won't return to an application, so we need to recreate. return true; } @@ -4041,11 +3843,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai + " onlyHasTaskOverlays=" + onlyHasTaskOverlays); } - if (mStackSupervisor.isFocusedStack(this) && task == topTask() && - task.isOverHomeStack()) { - mStackSupervisor.moveHomeStackTaskToTop(reason); - } - // The following block can be executed multiple times if there is more than one overlay. // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup // of the task by id and exiting early if not found. @@ -4567,60 +4364,19 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId); - boolean prevIsHome = false; + mTaskHistory.remove(tr); + mTaskHistory.add(0, tr); + updateTaskMovement(tr, false); - // If true, we should resume the home activity next if the task we are moving to the - // back is over the home stack. We force to false if the task we are moving to back - // is the home task and we don't want it resumed after moving to the back. - final boolean canGoHome = !tr.isActivityTypeHome() && tr.isOverHomeStack(); - if (canGoHome) { - final TaskRecord nextTask = getNextTask(tr); - if (nextTask != null) { - nextTask.setTaskToReturnTo(tr.getTaskToReturnTo()); - } else { - prevIsHome = true; - } - } - - boolean requiresMove = mTaskHistory.indexOf(tr) != 0; - if (requiresMove) { - mTaskHistory.remove(tr); - mTaskHistory.add(0, tr); - updateTaskMovement(tr, false); - - mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false); - mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController()); - } + mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false); + mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(), + true /* includingParents */); if (inPinnedWindowingMode()) { mStackSupervisor.removeStack(this); return true; } - // Otherwise, there is an assumption that moving a task to the back moves it behind the - // home activity. We make sure here that some activity in the stack will launch home. - int numTasks = mTaskHistory.size(); - for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) { - final TaskRecord task = mTaskHistory.get(taskNdx); - if (task.isOverHomeStack()) { - break; - } - if (taskNdx == 1) { - // Set the last task before tr to go to home. - task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - } - } - - final TaskRecord task = mResumedActivity != null ? mResumedActivity.getTask() : null; - if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) { - if (!mService.mBooting && !mService.mBooted) { - // Not ready yet! - return false; - } - tr.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); - return mStackSupervisor.resumeHomeStackTask(null, "moveTaskToBack"); - } - mStackSupervisor.resumeFocusedStackTopActivityLocked(); return true; } @@ -4851,69 +4607,43 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return didSomething; } - void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) { + /** + * @return The set of running tasks through {@param tasksOut} that are available to the caller. + * If {@param ignoreActivityType} or {@param ignoreWindowingMode} are not undefined, + * then skip running tasks that match those types. + */ + void getRunningTasks(List<TaskRecord> tasksOut, @ActivityType int ignoreActivityType, + @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed) { boolean focusedStack = mStackSupervisor.getFocusedStack() == this; boolean topTask = true; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); if (task.getTopActivity() == null) { + // Skip if there are no activities in the task continue; } - ActivityRecord r = null; - ActivityRecord top = null; - ActivityRecord tmp; - int numActivities = 0; - int numRunning = 0; - final ArrayList<ActivityRecord> activities = task.mActivities; if (!allowed && !task.isActivityTypeHome() && task.effectiveUid != callingUid) { + // Skip if the caller can't fetch this task continue; } - for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { - tmp = activities.get(activityNdx); - if (tmp.finishing) { - continue; - } - r = tmp; - - // Initialize state for next task if needed. - if (top == null || (top.state == ActivityState.INITIALIZING)) { - top = r; - numActivities = numRunning = 0; - } - - // Add 'r' into the current task. - numActivities++; - if (r.app != null && r.app.thread != null) { - numRunning++; - } - - if (DEBUG_ALL) Slog.v( - TAG, r.intent.getComponent().flattenToShortString() - + ": task=" + r.getTask()); + if (ignoreActivityType != ACTIVITY_TYPE_UNDEFINED + && task.getActivityType() == ignoreActivityType) { + // Skip ignored activity type + continue; + } + if (ignoreWindowingMode != WINDOWING_MODE_UNDEFINED + && task.getWindowingMode() == ignoreWindowingMode) { + // Skip ignored windowing mode + continue; } - - RunningTaskInfo ci = new RunningTaskInfo(); - ci.id = task.taskId; - ci.stackId = mStackId; - ci.baseActivity = r.intent.getComponent(); - ci.topActivity = top.intent.getComponent(); - ci.lastActiveTime = task.lastActiveTime; if (focusedStack && topTask) { - // Give the latest time to ensure foreground task can be sorted - // at the first, because lastActiveTime of creating task is 0. - ci.lastActiveTime = SystemClock.elapsedRealtime(); + // For the focused stack top task, update the last stack active time so that it can + // be used to determine the order of the tasks (it may not be set for newly created + // tasks) + task.lastActiveTime = SystemClock.elapsedRealtime(); topTask = false; } - - if (top.getTask() != null) { - ci.description = top.getTask().lastDescription; - } - ci.numActivities = numActivities; - ci.numRunning = numRunning; - ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode(); - ci.resizeMode = task.mResizeMode; - ci.configuration.setTo(task.getConfiguration()); - list.add(ci); + tasksOut.add(task); } } @@ -5057,14 +4787,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai onActivityRemovedFromStack(record); } - final int taskNdx = mTaskHistory.indexOf(task); - final int topTaskNdx = mTaskHistory.size() - 1; - if (task.isOverHomeStack() && taskNdx < topTaskNdx) { - final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1); - if (!nextTask.isOverHomeStack() && !nextTask.isOverAssistantStack()) { - nextTask.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - } - } mTaskHistory.remove(task); removeActivitiesFromLRUListLocked(task); updateTaskMovement(task, true); @@ -5094,7 +4816,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP && mStackSupervisor.isFocusedStack(this)) { String myReason = reason + " leftTaskHistoryEmpty"; - if (mFullscreen || !adjustFocusToNextFocusableStackLocked(myReason)) { + if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) { mStackSupervisor.moveHomeStackToFront(myReason); } } @@ -5123,24 +4845,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai addTask(task, toTop, "createTaskRecord"); final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY); - if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable() - && !isLockscreenShown) { + if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout) + && mBounds != null && task.isResizeable() && !isLockscreenShown) { task.updateOverrideConfiguration(mBounds); } task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); return task; } - boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) { - if (!task.inFreeformWindowingMode()) { - return false; - } - mStackSupervisor.getLaunchingTaskPositioner() - .updateDefaultBounds(task, mTaskHistory, windowLayout); - - return true; - } - ArrayList<TaskRecord> getAllTasks() { return new ArrayList<>(mTaskHistory); } @@ -5167,10 +4879,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mTaskHistory.add(position, task); task.setStack(this); - if (toTop) { - updateTaskReturnToForTopInsertion(task); - } - updateTaskMovement(task, toTop); postAddTask(task, prevStack, schedulePictureInPictureModeChange); @@ -5263,7 +4971,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai public String toString() { return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this)) + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType()) - + " mode=" + windowingModeToString(getWindowingMode()) + ", " + + " mode=" + windowingModeToString(getWindowingMode()) + + " visible=" + shouldBeVisible(null /* starting */) + ", " + mTaskHistory.size() + " tasks}"; } @@ -5290,7 +4999,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - super.writeToProto(proto, CONFIGURATION_CONTAINER); + super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); proto.write(ID, mStackId); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java index c15b5e2e..6ec158ef 100644 --- a/com/android/server/am/ActivityStackSupervisor.java +++ b/com/android/server/am/ActivityStackSupervisor.java @@ -110,7 +110,8 @@ import android.app.AppOpsManager; import android.app.ProfilerInfo; import android.app.ResultInfo; import android.app.WaitResult; -import android.app.WindowConfiguration; +import android.app.WindowConfiguration.ActivityType; +import android.app.WindowConfiguration.WindowingMode; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -223,13 +224,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // at the top of its container (e.g. stack). static final boolean ON_TOP = true; - // Used to indicate that an objects (e.g. task) removal from its container - // (e.g. stack) is due to it moving to another container. - static final boolean MOVING = true; - - // Force the focus to change to the stack we are moving a task to.. - static final boolean FORCE_FOCUS = true; - // Don't execute any calls to resume. static final boolean DEFER_RESUME = true; @@ -287,15 +281,19 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityManagerService mService; + /** The historial list of recent tasks including inactive tasks */ RecentTasks mRecentTasks; + /** Helper class to abstract out logic for fetching the set of currently running tasks */ + private RunningTasks mRunningTasks; + final ActivityStackSupervisorHandler mHandler; /** Short cut */ WindowManagerService mWindowManager; DisplayManager mDisplayManager; - LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner(); + private final LaunchingBoundsController mLaunchingBoundsController; /** Counter for next free stack ID to use for dynamic activity stacks. */ private int mNextFreeStackId = 0; @@ -405,6 +403,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * object each time. */ private final Rect tempRect = new Rect(); + private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic(); // The default minimal size that will be used if the activity doesn't specify its minimal size. // It will be calculated when the default display gets added. @@ -573,8 +572,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D public ActivityStackSupervisor(ActivityManagerService service, Looper looper) { mService = service; mHandler = new ActivityStackSupervisorHandler(looper); + mRunningTasks = createRunningTasks(); mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext); mKeyguardController = new KeyguardController(service, this); + + mLaunchingBoundsController = new LaunchingBoundsController(); + mLaunchingBoundsController.registerDefaultPositioners(this); } void setRecentTasks(RecentTasks recentTasks) { @@ -582,6 +585,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mRecentTasks.registerCallback(this); } + @VisibleForTesting + RunningTasks createRunningTasks() { + return new RunningTasks(); + } + /** * At the time when the constructor runs, the power manager has not yet been * initialized. So we initialize our wakelocks afterwards. @@ -685,10 +693,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return false; } - if (prev != null) { - prev.getTask().setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); - } - mHomeStack.moveHomeStackTaskToTop(); ActivityRecord r = getHomeActivity(); final String myReason = reason + " resumeHomeStackTask"; @@ -1160,43 +1164,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } - void getTasksLocked(int maxNum, List<RunningTaskInfo> list, int callingUid, boolean allowed) { - // Gather all of the running tasks for each stack into runningTaskLists. - ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists = new ArrayList<>(); - final int numDisplays = mActivityDisplays.size(); - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>(); - runningTaskLists.add(stackTaskList); - stack.getTasksLocked(stackTaskList, callingUid, allowed); - } - } - - // The lists are already sorted from most recent to oldest. Just pull the most recent off - // each list and add it to list. Stop when all lists are empty or maxNum reached. - while (maxNum > 0) { - long mostRecentActiveTime = Long.MIN_VALUE; - ArrayList<RunningTaskInfo> selectedStackList = null; - final int numTaskLists = runningTaskLists.size(); - for (int stackNdx = 0; stackNdx < numTaskLists; ++stackNdx) { - ArrayList<RunningTaskInfo> stackTaskList = runningTaskLists.get(stackNdx); - if (!stackTaskList.isEmpty()) { - final long lastActiveTime = stackTaskList.get(0).lastActiveTime; - if (lastActiveTime > mostRecentActiveTime) { - mostRecentActiveTime = lastActiveTime; - selectedStackList = stackTaskList; - } - } - } - if (selectedStackList != null) { - list.add(selectedStackList.remove(0)); - --maxNum; - } else { - break; - } - } + @VisibleForTesting + void getRunningTasks(int maxNum, List<RunningTaskInfo> list, + @ActivityType int ignoreActivityType, @WindowingMode int ignoreWindowingMode, + int callingUid, boolean allowed) { + mRunningTasks.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, + mActivityDisplays, callingUid, allowed); } ActivityInfo resolveActivity(Intent intent, ResolveInfo rInfo, int startFlags, @@ -1592,7 +1565,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return false; } if (options != null) { - if (options.getLaunchTaskId() != INVALID_STACK_ID) { + // If a launch task id is specified, then ensure that the caller is the recents + // component or has the START_TASKS_FROM_RECENTS permission + if (options.getLaunchTaskId() != INVALID_TASK_ID + && !mRecentTasks.isCallerRecents(callingUid)) { final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS, callingPid, callingUid); if (startInTaskPerm == PERMISSION_DENIED) { @@ -2097,23 +2073,28 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - void findTaskToMoveToFrontLocked(TaskRecord task, int flags, ActivityOptions options, + void findTaskToMoveToFront(TaskRecord task, int flags, ActivityOptions options, String reason, boolean forceNonResizeable) { - if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { - mUserLeaving = true; - } - if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) { - // Caller wants the home activity moved with it. To accomplish this, - // we'll just indicate that this task returns to the home task. - task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - } final ActivityStack currentStack = task.getStack(); if (currentStack == null) { - Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task=" + Slog.e(TAG, "findTaskToMoveToFront: can't move task=" + task + " to front. Stack is null"); return; } + if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { + mUserLeaving = true; + } + + final ActivityRecord prev = topRunningActivityLocked(); + + if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0 + || (prev != null && prev.isActivityTypeRecents())) { + // Caller wants the home activity moved with it or the previous task is recents in which + // case we always return home from the task we are moving to the front. + moveHomeStackToFront("findTaskToMoveToFront"); + } + if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) { final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds()); task.updateOverrideConfiguration(bounds); @@ -2122,7 +2103,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (stack != currentStack) { task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME, - "findTaskToMoveToFrontLocked"); + "findTaskToMoveToFront"); stack = currentStack; // moveTaskToStackUncheckedLocked() should already placed the task on top, // still need moveTaskToFrontLocked() below for any transition settings. @@ -2161,8 +2142,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D || mService.mSupportsFreeformWindowManagement; } - LaunchingTaskPositioner getLaunchingTaskPositioner() { - return mTaskPositioner; + LaunchingBoundsController getLaunchingBoundsController() { + return mLaunchingBoundsController; } protected <T extends ActivityStack> T getStack(int stackId) { @@ -2186,89 +2167,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } - /** - * Returns true if the {@param windowingMode} is supported based on other parameters passed in. - * @param windowingMode The windowing mode we are checking support for. - * @param supportsMultiWindow If we should consider support for multi-window mode in general. - * @param supportsSplitScreen If we should consider support for split-screen multi-window. - * @param supportsFreeform If we should consider support for freeform multi-window. - * @param supportsPip If we should consider support for picture-in-picture mutli-window. - * @param activityType The activity type under consideration. - * @return true if the windowing mode is supported. - */ - boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow, - boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip, - int activityType) { - - if (windowingMode == WINDOWING_MODE_UNDEFINED - || windowingMode == WINDOWING_MODE_FULLSCREEN) { - return true; - } - if (!supportsMultiWindow) { - return false; - } - - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode( - windowingMode, activityType); - } - - if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) { - return false; - } - - if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) { - return false; - } - return true; - } - - private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options, - @Nullable TaskRecord task, int activityType) { - - // First preference if the windowing mode in the activity options if set. - int windowingMode = (options != null) - ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED; - - // If windowing mode is unset, then next preference is the candidate task, then the - // activity record. - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - if (task != null) { - windowingMode = task.getWindowingMode(); - } - if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) { - windowingMode = r.getWindowingMode(); - } - } - - // Make sure the windowing mode we are trying to use makes sense for what is supported. - boolean supportsMultiWindow = mService.mSupportsMultiWindow; - boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow; - boolean supportsFreeform = mService.mSupportsFreeformWindowManagement; - boolean supportsPip = mService.mSupportsPictureInPicture; - if (supportsMultiWindow) { - if (task != null) { - supportsMultiWindow = task.isResizeable(); - supportsSplitScreen = task.supportsSplitScreenWindowingMode(); - // TODO: Do we need to check for freeform and Pip support here? - } else if (r != null) { - supportsMultiWindow = r.isResizeable(); - supportsSplitScreen = r.supportsSplitScreenWindowingMode(); - supportsFreeform = r.supportsFreeform(); - supportsPip = r.supportsPictureInPicture(); - } - } - - if (windowingMode != WINDOWING_MODE_UNDEFINED - && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen, - supportsFreeform, supportsPip, activityType)) { - return windowingMode; - } - // Return root/systems windowing mode - return getWindowingMode(); - } - int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options, @Nullable TaskRecord task) { // Preference is given to the activity type for the activity then the task since the type @@ -2329,7 +2227,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } final int activityType = resolveActivityType(r, options, candidateTask); - int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType); T stack = null; // Next preference for stack goes to the display Id set in the activity options or the @@ -2347,7 +2244,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId); if (display != null) { - stack = display.getOrCreateStack(windowingMode, activityType, onTop); + stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop); if (stack != null) { return stack; } @@ -2365,10 +2262,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D stack = r.getStack(); } if (stack != null) { - if (stack.isCompatible(windowingMode, activityType)) { - return stack; - } display = stack.getDisplay(); + if (display != null) { + final int windowingMode = + display.resolveWindowingMode(r, options, candidateTask, activityType); + if (stack.isCompatible(windowingMode, activityType)) { + return stack; + } + } } if (display == null @@ -2379,7 +2280,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D display = getDefaultDisplay(); } - return display.getOrCreateStack(windowingMode, activityType, onTop); + return display.getOrCreateStack(r, options, candidateTask, activityType, onTop); } /** @@ -2596,6 +2497,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId); if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + // Tell the display we are exiting split-screen mode. + toDisplay.onExitingSplitScreenMode(); // We are moving all tasks from the docked stack to the fullscreen stack, // which is dismissing the docked stack, so resize all other stacks to // fullscreen here already so we don't end up with resize trashing. @@ -2625,35 +2528,27 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ArrayList<TaskRecord> tasks = fromStack.getAllTasks(); if (!tasks.isEmpty()) { + mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); final int size = tasks.size(); - final ActivityStack fullscreenStack = toDisplay.getOrCreateStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop); - - if (onTop) { - final int returnToType = - toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED); - for (int i = 0; i < size; i++) { - final TaskRecord task = tasks.get(i); + for (int i = 0; i < size; ++i) { + final TaskRecord task = tasks.get(i); + final ActivityStack toStack = toDisplay.getOrCreateStack( + null, mTmpOptions, task, task.getActivityType(), onTop); + + if (onTop) { + final int returnToType = + toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED); final boolean isTopTask = i == (size - 1); - if (inPinnedWindowingMode) { - // Update the return-to to reflect where the pinned stack task was moved - // from so that we retain the stack that was previously visible if the - // pinned stack is recreated. See moveActivityToPinnedStackLocked(). - task.setTaskToReturnTo(returnToType); - } // Defer resume until all the tasks have been moved to the fullscreen stack - task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, + task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME, schedulePictureInPictureModeChange, "moveTasksToFullscreenStack - onTop"); - } - } else { - for (int i = 0; i < size; i++) { - final TaskRecord task = tasks.get(i); + } else { // Position the tasks in the fullscreen stack in order at the bottom of the // stack. Also defer resume until all the tasks have been moved to the // fullscreen stack. - task.reparent(fullscreenStack, i /* position */, + task.reparent(toStack, ON_TOP, REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME, schedulePictureInPictureModeChange, "moveTasksToFullscreenStack - NOT_onTop"); @@ -3080,23 +2975,16 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D + " task=" + task); } - // We don't allow moving a unresizeable task to the docked stack since the docked stack is - // used for split-screen mode and will cause things like the docked divider to show up. We - // instead leave the task in its current stack or move it to the fullscreen stack if it - // isn't currently in a stack. + // Leave the task in its current stack or a fullscreen stack if it isn't resizeable and the + // preferred stack is in multi-window mode. if (inMultiWindowMode && !task.isResizeable()) { - Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack." - + " Moving to stackId=" + stackId + " instead."); - // Temporarily disable resizeablility of the task as we don't want it to be resized if, - // for example, a docked stack is created which will lead to the stack we are moving - // from being resized and and its resizeable tasks being resized. - try { - task.mTemporarilyUnresizable = true; - stack = stack.getDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop); - } finally { - task.mTemporarilyUnresizable = false; + Slog.w(TAG, "Can not move unresizeable task=" + task + " to multi-window stack=" + stack + + " Moving to a fullscreen stack instead."); + if (prevStack != null) { + return prevStack; } + stack = stack.getDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop); } return stack; } @@ -3123,12 +3011,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } moveActivityToPinnedStackLocked(r, null /* sourceBounds */, 0f /* aspectRatio */, - true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack"); + "moveTopActivityToPinnedStack"); return true; } void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio, - boolean moveHomeStackToFront, String reason) { + String reason) { mWindowManager.deferSurfaceLayout(); @@ -3154,17 +3042,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D true /* allowResizeInDockedMode */, !DEFER_RESUME); if (task.mActivities.size() == 1) { - // There is only one activity in the task. So, we can just move the task over to - // the stack without re-parenting the activity in a different task. We don't - // move the home stack forward if we are currently entering picture-in-picture - // while pausing because that changes the focused stack and may prevent the new - // starting activity from resuming. - if (moveHomeStackToFront && task.returnsToHomeTask() - && (r.state == RESUMED || !r.supportsEnterPipOnTaskSwitch)) { - // Move the home stack forward if the task we just moved to the pinned stack - // was launched from home so home should be visible behind it. - moveHomeStackToFront(reason); - } // Defer resume until below, and do not schedule PiP changes until we animate below task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason); @@ -3799,7 +3676,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } public void writeToProto(ProtoOutputStream proto) { - super.writeToProto(proto, CONFIGURATION_CONTAINER); + super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx); activityDisplay.writeToProto(proto, DISPLAYS); @@ -4287,9 +4164,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final boolean isSecondaryDisplayPreferred = (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY); final boolean inSplitScreenMode = actualStack != null - && actualStack.inSplitScreenWindowingMode(); + && actualStack.getDisplay().hasSplitScreenPrimaryStack(); if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) - && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) { + && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) { return; } @@ -4606,6 +4483,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D "startActivityFromRecents: Task " + taskId + " not found."); } + // We always want to return to the home activity instead of the recents activity from + // whatever is started from the recents activity, so move the home stack forward. + moveHomeStackToFront("startActivityFromRecents"); + // If the user must confirm credentials (e.g. when first launching a work app and the // Work Challenge is present) let startActivityInPackage handle the intercepting. if (!mService.mUserController.shouldConfirmCredentials(task.userId) diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java index 6f74d851..1c802827 100644 --- a/com/android/server/am/ActivityStarter.java +++ b/com/android/server/am/ActivityStarter.java @@ -26,9 +26,6 @@ import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER; import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; @@ -151,7 +148,7 @@ class ActivityStarter { private boolean mLaunchTaskBehind; private int mLaunchFlags; - private Rect mLaunchBounds; + private Rect mLaunchBounds = new Rect(); private ActivityRecord mNotTop; private boolean mDoResume; @@ -169,9 +166,6 @@ class ActivityStarter { private Intent mNewTaskIntent; private ActivityStack mSourceStack; private ActivityStack mTargetStack; - // Indicates that we moved other task and are going to put something on top soon, so - // we don't want to show it redundantly or accidentally change what's shown below. - private boolean mMovedOtherTask; private boolean mMovedToFront; private boolean mNoAnimation; private boolean mKeepCurTransition; @@ -210,7 +204,7 @@ class ActivityStarter { mLaunchFlags = 0; mLaunchMode = INVALID_LAUNCH_MODE; - mLaunchBounds = null; + mLaunchBounds.setEmpty(); mNotTop = null; mDoResume = false; @@ -227,7 +221,6 @@ class ActivityStarter { mSourceStack = null; mTargetStack = null; - mMovedOtherTask = false; mMovedToFront = false; mNoAnimation = false; mKeepCurTransition = false; @@ -1184,12 +1177,8 @@ class ActivityStarter { mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId); mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent, mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid)); - if (mSourceRecord != null) { - mStartActivity.getTask().setTaskToReturnTo(mSourceRecord); - } if (newTask) { - EventLog.writeEvent( - EventLogTags.AM_CREATE_TASK, mStartActivity.userId, + EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.userId, mStartActivity.getTask().taskId); } ActivityStack.logStartActivity( @@ -1254,7 +1243,10 @@ class ActivityStarter { mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options); - mLaunchBounds = getOverrideBounds(r, options, inTask); + mLaunchBounds.setEmpty(); + + mSupervisor.getLaunchingBoundsController().calculateBounds(inTask, null /*layout*/, r, + sourceRecord, options, mLaunchBounds); mLaunchMode = r.launchMode; @@ -1579,7 +1571,6 @@ class ActivityStarter { if (mLaunchTaskBehind && mSourceRecord != null) { intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask()); } - mMovedOtherTask = true; // If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities // will be cleared soon by ActivityStarter in setTaskFromIntentActivity(). @@ -1644,7 +1635,6 @@ class ActivityStarter { intentActivity.showStartingWindow(null /* prev */, false /* newTask */, true /* taskSwitch */); } - updateTaskReturnToType(intentActivity.getTask(), mLaunchFlags, focusStack); } } if (!mMovedToFront && mDoResume) { @@ -1663,27 +1653,6 @@ class ActivityStarter { return intentActivity; } - private void updateTaskReturnToType( - TaskRecord task, int launchFlags, ActivityStack focusedStack) { - if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { - // Caller wants to appear on home activity. - task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - return; - } else if (focusedStack == null || focusedStack.isActivityTypeHome()) { - // Task will be launched over the home stack, so return home. - task.setTaskToReturnTo(ACTIVITY_TYPE_HOME); - return; - } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) { - // Task was launched over the assistant stack, so return there - task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); - return; - } - - // Else we are coming from an application stack so return to an application. - task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD); - } - private void setTaskFromIntentActivity(ActivityRecord intentActivity) { if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { @@ -1700,11 +1669,6 @@ class ActivityStarter { task.performClearTaskLocked(); mReuseTask = task; mReuseTask.setIntent(mStartActivity); - - // When we clear the task - focus will be adjusted, which will bring another task - // to top before we launch the activity we need. This will temporary swap their - // mTaskToReturnTo values and we don't want to overwrite them accidentally. - mMovedOtherTask = true; } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) { ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity, @@ -1725,7 +1689,7 @@ class ActivityStarter { // Target stack got cleared when we all activities were removed above. // Go ahead and reset it. mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */, - null /* bounds */, mLaunchFlags, mOptions); + mLaunchFlags, mOptions); mTargetStack.addTask(task, !mLaunchTaskBehind /* toTop */, "startActivityUnchecked"); } @@ -1776,8 +1740,7 @@ class ActivityStarter { private int setTaskFromReuseOrCreateNewTask( TaskRecord taskToAffiliate, ActivityStack topStack) { - mTargetStack = computeStackFocus( - mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions); + mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions); // Do no move the target stack to front yet, as we might bail if // isLockTaskModeViolation fails below. @@ -1806,15 +1769,6 @@ class ActivityStarter { return START_RETURN_LOCK_TASK_MODE_VIOLATION; } - if (!mMovedOtherTask) { - // If stack id is specified in activity options, usually it means that activity is - // launched not from currently focused stack (e.g. from SysUI or from shell) - in - // that case we check the target stack. - // TODO: Not sure I understand the value or use of the commented out code and the - // comment above. See if this causes any issues and why... - updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags, - /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack); - } if (mDoResume) { mTargetStack.moveToFront("reuseOrNewTask"); } @@ -1962,7 +1916,7 @@ class ActivityStarter { return START_TASK_TO_FRONT; } - if (mLaunchBounds != null) { + if (!mLaunchBounds.isEmpty()) { // TODO: Shouldn't we already know what stack to use by the time we get here? ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP); if (stack != mInTask.getStack()) { @@ -1985,7 +1939,7 @@ class ActivityStarter { } void updateBounds(TaskRecord task, Rect bounds) { - if (bounds == null) { + if (bounds.isEmpty()) { return; } @@ -1998,8 +1952,7 @@ class ActivityStarter { } private void setTaskToCurrentTopOrCreateNewTask() { - mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags, - mOptions); + mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions); if (mDoResume) { mTargetStack.moveToFront("addingToTopTask"); } @@ -2062,8 +2015,8 @@ class ActivityStarter { } } - private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds, - int launchFlags, ActivityOptions aOptions) { + private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, int launchFlags, + ActivityOptions aOptions) { final TaskRecord task = r.getTask(); ActivityStack stack = getLaunchStack(r, launchFlags, task, aOptions); if (stack != null) { @@ -2214,15 +2167,6 @@ class ActivityStarter { } } - private Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) { - Rect newBounds = null; - if (mSupervisor.canUseActivityOptionsLaunchBounds(options) - && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) { - newBounds = TaskRecord.validateBounds(options.getLaunchBounds()); - } - return newBounds; - } - private boolean isLaunchModeOneOf(int mode1, int mode2) { return mode1 == mLaunchMode || mode2 == mLaunchMode; } diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java index 38b3039f..17626ea1 100644 --- a/com/android/server/am/AppTaskImpl.java +++ b/com/android/server/am/AppTaskImpl.java @@ -82,7 +82,7 @@ class AppTaskImpl extends IAppTask.Stub { if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } - return RecentTasks.createRecentTaskInfo(tr); + return mService.getRecentTasks().createRecentTaskInfo(tr); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/com/android/server/am/BroadcastQueue.java b/com/android/server/am/BroadcastQueue.java index c62cc38b..aa82d000 100644 --- a/com/android/server/am/BroadcastQueue.java +++ b/com/android/server/am/BroadcastQueue.java @@ -1198,11 +1198,6 @@ public final class BroadcastQueue { + " (uid " + r.callingUid + ")"); skip = true; } - if (!skip) { - r.manifestCount++; - } else { - r.manifestSkipCount++; - } if (r.curApp != null && r.curApp.crashing) { // If the target process is crashing, just skip it. Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r @@ -1283,6 +1278,16 @@ public final class BroadcastQueue { } } + if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction()) + && !mService.mUserController + .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid), + 0 /* flags */)) { + skip = true; + Slog.w(TAG, + "Skipping delivery to " + info.activityInfo.packageName + " / " + + info.activityInfo.applicationInfo.uid + " : user is not running"); + } + if (skip) { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Skipping delivery of ordered [" + mQueueName + "] " @@ -1291,9 +1296,11 @@ public final class BroadcastQueue { r.receiver = null; r.curFilter = null; r.state = BroadcastRecord.IDLE; + r.manifestSkipCount++; scheduleBroadcastsLocked(); return; } + r.manifestCount++; r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED; r.state = BroadcastRecord.APP_RECEIVE; @@ -1302,7 +1309,7 @@ public final class BroadcastQueue { if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) { Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, " + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = " - + info.activityInfo.applicationInfo.uid); + + receiverUid); } if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) { @@ -1365,7 +1372,7 @@ public final class BroadcastQueue { // and mark the broadcast record as ready for the next. Slog.w(TAG, "Unable to launch app " + info.activityInfo.applicationInfo.packageName + "/" - + info.activityInfo.applicationInfo.uid + " for broadcast " + + receiverUid + " for broadcast " + r.intent + ": process is bad"); logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, diff --git a/com/android/server/am/LaunchingActivityPositioner.java b/com/android/server/am/LaunchingActivityPositioner.java new file mode 100644 index 00000000..d5f9cf3a --- /dev/null +++ b/com/android/server/am/LaunchingActivityPositioner.java @@ -0,0 +1,63 @@ +/* + * 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.ActivityOptions; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; +import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner; + +/** + * An implementation of {@link LaunchingBoundsPositioner}, which applies the launch bounds specified + * inside {@link ActivityOptions#getLaunchBounds()}. + */ +public class LaunchingActivityPositioner implements LaunchingBoundsPositioner { + private final ActivityStackSupervisor mSupervisor; + + LaunchingActivityPositioner(ActivityStackSupervisor activityStackSupervisor) { + mSupervisor = activityStackSupervisor; + } + + @Override + public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout, + ActivityRecord activity, ActivityRecord source, + ActivityOptions options, Rect current, Rect result) { + // We only care about figuring out bounds for activities. + if (activity == null) { + return RESULT_SKIP; + } + + // Activity must be resizeable in the specified task. + if (!(mSupervisor.canUseActivityOptionsLaunchBounds(options) + && (activity.isResizeable() || (task != null && task.isResizeable())))) { + return RESULT_SKIP; + } + + final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds()); + + // Bounds weren't valid. + if (bounds == null) { + return RESULT_SKIP; + } + + result.set(bounds); + + // When this is the most explicit position specification so we should not allow further + // modification of the position. + return RESULT_DONE; + } +} diff --git a/com/android/server/am/LaunchingBoundsController.java b/com/android/server/am/LaunchingBoundsController.java new file mode 100644 index 00000000..c762f7f4 --- /dev/null +++ b/com/android/server/am/LaunchingBoundsController.java @@ -0,0 +1,163 @@ +/* + * 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.IntDef; +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo.WindowLayout; +import android.graphics.Rect; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP; + +/** + * {@link LaunchingBoundsController} calculates the launch bounds by coordinating between registered + * {@link LaunchingBoundsPositioner}. + */ +class LaunchingBoundsController { + private final List<LaunchingBoundsPositioner> mPositioners = new ArrayList<>(); + + // Temporary {@link Rect} for calculations. This is kept separate from {@code mTmpCurrent} and + // {@code mTmpResult} to prevent clobbering values. + private final Rect mTmpRect = new Rect(); + + private final Rect mTmpCurrent = new Rect(); + private final Rect mTmpResult = new Rect(); + + /** + * Creates a {@link LaunchingBoundsController} with default registered + * {@link LaunchingBoundsPositioner}s. + */ + void registerDefaultPositioners(ActivityStackSupervisor supervisor) { + // {@link LaunchingTaskPositioner} handles window layout preferences. + registerPositioner(new LaunchingTaskPositioner()); + + // {@link LaunchingActivityPositioner} is the most specific positioner and thus should be + // registered last (applied first) out of the defaults. + registerPositioner(new LaunchingActivityPositioner(supervisor)); + } + + /** + * Returns the position calculated by the registered positioners + * @param task The {@link TaskRecord} currently being positioned. + * @param layout The specified {@link WindowLayout}. + * @param activity The {@link ActivityRecord} currently being positioned. + * @param source The {@link ActivityRecord} from which activity was started from. + * @param options The {@link ActivityOptions} specified for the activity. + * @param result The resulting bounds. If no bounds are set, {@link Rect#isEmpty()} will be + * {@code true}. + */ + void calculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityRecord source, ActivityOptions options, Rect result) { + result.setEmpty(); + + // We start at the last registered {@link LaunchingBoundsPositioner} as this represents + // The positioner closest to the product level. Moving back through the list moves closer to + // the platform logic. + for (int i = mPositioners.size() - 1; i >= 0; --i) { + mTmpResult.setEmpty(); + mTmpCurrent.set(result); + final LaunchingBoundsPositioner positioner = mPositioners.get(i); + + switch(positioner.onCalculateBounds(task, layout, activity, source, options, + mTmpCurrent, mTmpResult)) { + case RESULT_SKIP: + // Do not apply any results when we are told to skip + continue; + case RESULT_DONE: + // Set result and return immediately. + result.set(mTmpResult); + return; + case RESULT_CONTINUE: + // Set result and continue + result.set(mTmpResult); + break; + } + } + } + + /** + * A convenience method for laying out a task. + * @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); + + if (mTmpRect.isEmpty()) { + return false; + } + + task.updateOverrideConfiguration(mTmpRect); + + return true; + } + + /** + * Adds a positioner to participate in future bounds calculation. Note that the last registered + * {@link LaunchingBoundsPositioner} will be the first to calculate the bounds. + */ + void registerPositioner(LaunchingBoundsPositioner positioner) { + if (mPositioners.contains(positioner)) { + return; + } + + mPositioners.add(positioner); + } + + /** + * An interface implemented by those wanting to participate in bounds calculation. + */ + interface LaunchingBoundsPositioner { + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE}) + @interface Result {} + + // Returned when the positioner does not want to influence the bounds calculation + int RESULT_SKIP = 0; + // Returned when the positioner has changed the bounds and would like its results to be the + // final bounds applied. + int RESULT_DONE = 1; + // Returned when the positioner has changed the bounds but is okay with other positioners + // influencing the bounds. + int RESULT_CONTINUE = 2; + + /** + * Called when asked to calculate bounds. + * @param task The {@link TaskRecord} currently being positioned. + * @param layout The specified {@link WindowLayout}. + * @param activity The {@link ActivityRecord} currently being positioned. + * @param source The {@link ActivityRecord} activity was started from. + * @param options The {@link ActivityOptions} specified for the activity. + * @param current The current bounds. This can differ from the initial bounds as it + * represents the modified bounds up to this point. + * @param result The {@link Rect} which the positioner should return its modified bounds. + * Any merging of the current bounds should be already applied to this + * value as well before returning. + * @return A {@link Result} representing the result of the bounds calculation. + */ + @Result + int onCalculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityRecord source, ActivityOptions options, Rect current, Rect result); + } +} diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java index 0dc73e98..c958fcac 100644 --- a/com/android/server/am/LaunchingTaskPositioner.java +++ b/com/android/server/am/LaunchingTaskPositioner.java @@ -19,7 +19,7 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; +import android.app.ActivityOptions; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; @@ -36,8 +36,10 @@ import java.util.ArrayList; * and compares corners of the task with corners of existing tasks. If some two pairs of corners are * sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts * all possible shifts, it gives up and puts the task in the original position. + * + * Note that the only gravities of concern are the corners and the center. */ -class LaunchingTaskPositioner { +class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoundsPositioner { private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchingTaskPositioner" : TAG_AM; // Determines how close window frames/corners have to be to call them colliding. @@ -74,44 +76,51 @@ class LaunchingTaskPositioner { * Tries to set task's bound in a way that it won't collide with any other task. By colliding * we mean that two tasks have left-top corner very close to each other, so one might get * obfuscated by the other one. - * - * @param task Task for which we want to find bounds that won't collide with other. - * @param tasks Existing tasks with which we don't want to collide. - * @param windowLayout Optional information from the client about how it would like to be sized - * and positioned. */ - void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks, - @Nullable ActivityInfo.WindowLayout windowLayout) { + @Override + public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout, + ActivityRecord activity, ActivityRecord source, + ActivityOptions options, Rect current, Rect result) { + // We can only apply positioning if we're in a freeform stack. + if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) { + return RESULT_SKIP; + } + + final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks(); + updateAvailableRect(task, mAvailableRect); - if (windowLayout == null) { - positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect), - getFreeformHeight(mAvailableRect)); - return; + if (layout == null) { + positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect), + getFreeformHeight(mAvailableRect), result); + return RESULT_CONTINUE; } - int width = getFinalWidth(windowLayout, mAvailableRect); - int height = getFinalHeight(windowLayout, mAvailableRect); - int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK; - int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + + int width = getFinalWidth(layout, mAvailableRect); + int height = getFinalHeight(layout, mAvailableRect); + int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK; + int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; if (verticalGravity == Gravity.TOP) { if (horizontalGravity == Gravity.RIGHT) { - positionTopRight(task, tasks, mAvailableRect, width, height); + positionTopRight(tasks, mAvailableRect, width, height, result); } else { - positionTopLeft(task, tasks, mAvailableRect, width, height); + positionTopLeft(tasks, mAvailableRect, width, height, result); } } else if (verticalGravity == Gravity.BOTTOM) { if (horizontalGravity == Gravity.RIGHT) { - positionBottomRight(task, tasks, mAvailableRect, width, height); + positionBottomRight(tasks, mAvailableRect, width, height, result); } else { - positionBottomLeft(task, tasks, mAvailableRect, width, height); + positionBottomLeft(tasks, mAvailableRect, width, height, result); } } else { // Some fancy gravity setting that we don't support yet. We just put the activity in the // center. - Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity + Slog.w(TAG, "Received unsupported gravity: " + layout.gravity + ", positioning in the center instead."); - positionCenter(task, tasks, mAvailableRect, width, height); + positionCenter(tasks, mAvailableRect, width, height, result); } + + return RESULT_CONTINUE; } private void updateAvailableRect(TaskRecord task, Rect availableRect) { @@ -179,50 +188,50 @@ class LaunchingTaskPositioner { return height; } - private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { mTmpProposal.set(availableRect.left, availableRect.bottom - height, availableRect.left + width, availableRect.bottom); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_RIGHT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT, + result); } - private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { mTmpProposal.set(availableRect.right - width, availableRect.bottom - height, availableRect.right, availableRect.bottom); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_LEFT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT, + result); } - private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { mTmpProposal.set(availableRect.left, availableRect.top, availableRect.left + width, availableRect.top + height); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_RIGHT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT, + result); } - private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { mTmpProposal.set(availableRect.right - width, availableRect.top, availableRect.right, availableRect.top + height); - position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_LEFT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT, + result); } - private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width, + int height, Rect result) { final int defaultFreeformLeft = getFreeformStartLeft(availableRect); final int defaultFreeformTop = getFreeformStartTop(availableRect); mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop, defaultFreeformLeft + width, defaultFreeformTop + height); - position(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART, - SHIFT_POLICY_DIAGONAL_DOWN); + position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN, + result); } - private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect, - Rect proposal, boolean allowRestart, int shiftPolicy) { + private void position(ArrayList<TaskRecord> tasks, Rect availableRect, + Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) { mTmpOriginal.set(proposal); boolean restarted = false; while (boundsConflict(proposal, tasks)) { @@ -252,7 +261,7 @@ class LaunchingTaskPositioner { break; } } - task.updateOverrideConfiguration(proposal); + result.set(proposal); } private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) { diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java index 1c094c17..4b2a0843 100644 --- a/com/android/server/am/LockTaskController.java +++ b/com/android/server/am/LockTaskController.java @@ -19,17 +19,11 @@ package com.android.server.am; import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; -import static android.app.StatusBarManager.DISABLE_BACK; -import static android.app.StatusBarManager.DISABLE_HOME; -import static android.app.StatusBarManager.DISABLE_MASK; -import static android.app.StatusBarManager.DISABLE_NONE; -import static android.app.StatusBarManager.DISABLE_RECENT; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Context.DEVICE_POLICY_SERVICE; import static android.content.Context.STATUS_BAR_SERVICE; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_CURRENT; -import static android.provider.Settings.Secure.LOCK_TO_APP_EXIT_LOCKED; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; @@ -46,7 +40,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; +import android.app.StatusBarManager; +import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; +import android.content.ComponentName; import android.content.Context; import android.os.Binder; import android.os.Debug; @@ -55,6 +52,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -84,13 +82,39 @@ public class LockTaskController { private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK; @VisibleForTesting - static final int STATUS_BAR_MASK_LOCKED = DISABLE_MASK - & (~DISABLE_BACK); + static final int STATUS_BAR_MASK_LOCKED = StatusBarManager.DISABLE_MASK + & (~StatusBarManager.DISABLE_EXPAND) + & (~StatusBarManager.DISABLE_NOTIFICATION_TICKER) + & (~StatusBarManager.DISABLE_SYSTEM_INFO) + & (~StatusBarManager.DISABLE_BACK); @VisibleForTesting - static final int STATUS_BAR_MASK_PINNED = DISABLE_MASK - & (~DISABLE_BACK) - & (~DISABLE_HOME) - & (~DISABLE_RECENT); + static final int STATUS_BAR_MASK_PINNED = StatusBarManager.DISABLE_MASK + & (~StatusBarManager.DISABLE_BACK) + & (~StatusBarManager.DISABLE_HOME) + & (~StatusBarManager.DISABLE_RECENT); + + private static final SparseArray<Pair<Integer, Integer>> STATUS_BAR_FLAG_MAP_LOCKED; + static { + STATUS_BAR_FLAG_MAP_LOCKED = new SparseArray<>(); + + STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO, + new Pair<>(StatusBarManager.DISABLE_CLOCK, StatusBarManager.DISABLE2_SYSTEM_ICONS)); + + STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS, + new Pair<>(StatusBarManager.DISABLE_NOTIFICATION_ICONS + | StatusBarManager.DISABLE_NOTIFICATION_ALERTS, + StatusBarManager.DISABLE2_NOTIFICATION_SHADE)); + + STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_HOME, + new Pair<>(StatusBarManager.DISABLE_HOME, StatusBarManager.DISABLE2_NONE)); + + STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS, + new Pair<>(StatusBarManager.DISABLE_RECENT, StatusBarManager.DISABLE2_NONE)); + + STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS, + new Pair<>(StatusBarManager.DISABLE_NONE, + StatusBarManager.DISABLE2_GLOBAL_ACTIONS)); + } /** Tag used for disabling of keyguard */ private static final String LOCK_TASK_TAG = "Lock-to-App"; @@ -131,6 +155,11 @@ public class LockTaskController { private final SparseArray<String[]> mLockTaskPackages = new SparseArray<>(); /** + * Features that are allowed by DPC to show during LockTask mode. + */ + private final SparseArray<Integer> mLockTaskFeatures = new SparseArray<>(); + + /** * Store the current lock task mode. Possible values: * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED}, * {@link ActivityManager#LOCK_TASK_MODE_PINNED} @@ -319,30 +348,17 @@ public class LockTaskController { private void performStopLockTask(int userId) { // When lock task ends, we enable the status bars. try { - if (getStatusBarService() != null) { - getStatusBarService().disable(DISABLE_NONE, mToken, - mContext.getPackageName()); + setStatusBarState(LOCK_TASK_MODE_NONE, userId); + setKeyguardState(LOCK_TASK_MODE_NONE, userId); + if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { + lockKeyguardIfNeeded(); } - mWindowManager.reenableKeyguard(mToken); if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId); } if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { getLockTaskNotify().showPinningExitToast(); } - try { - boolean shouldLockKeyguard = Settings.Secure.getIntForUser( - mContext.getContentResolver(), - LOCK_TO_APP_EXIT_LOCKED, - USER_CURRENT) != 0; - if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) { - mWindowManager.lockNow(null); - mWindowManager.dismissKeyguard(null /* callback */); - getLockPatternUtils().requireCredentialEntry(USER_ALL); - } - } catch (Settings.SettingNotFoundException e) { - // No setting, don't lock. - } } catch (RemoteException ex) { throw new RuntimeException(ex); } finally { @@ -430,7 +446,7 @@ public class LockTaskController { } if (andResume) { - mSupervisor.findTaskToMoveToFrontLocked(task, 0, null, reason, + mSupervisor.findTaskToMoveToFront(task, 0, null, reason, lockTaskModeState != LOCK_TASK_MODE_NONE); mSupervisor.resumeFocusedStackTopActivityLocked(); mWindowManager.executeAppTransition(); @@ -448,16 +464,8 @@ public class LockTaskController { getLockTaskNotify().showPinningStartToast(); } mLockTaskModeState = lockTaskModeState; - if (getStatusBarService() != null) { - int flags = 0; - if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) { - flags = STATUS_BAR_MASK_LOCKED; - } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { - flags = STATUS_BAR_MASK_PINNED; - } - getStatusBarService().disable(flags, mToken, mContext.getPackageName()); - } - mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); + setStatusBarState(lockTaskModeState, userId); + setKeyguardState(lockTaskModeState, userId); if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(true, packageName, userId); } @@ -536,6 +544,135 @@ public class LockTaskController { } /** + * Update the UI features that are enabled for LockTask mode. + * @param userId Which user these feature flags are associated with + * @param flags Bitfield of feature flags + * @see DevicePolicyManager#setLockTaskFeatures(ComponentName, int) + */ + void updateLockTaskFeatures(int userId, int flags) { + int oldFlags = getLockTaskFeaturesForUser(userId); + if (flags == oldFlags) { + return; + } + + mLockTaskFeatures.put(userId, flags); + TaskRecord lockedTask = getLockedTask(); + if (lockedTask != null && userId == lockedTask.userId) { + mHandler.post(() -> { + if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) { + setStatusBarState(mLockTaskModeState, userId); + setKeyguardState(mLockTaskModeState, userId); + } + }); + } + } + + /** + * Helper method for configuring the status bar disabled state. + * Should only be called on the handler thread to avoid race. + */ + private void setStatusBarState(int lockTaskModeState, int userId) { + IStatusBarService statusBar = getStatusBarService(); + if (statusBar == null) { + Slog.e(TAG, "Can't find StatusBarService"); + return; + } + + // Default state, when lockTaskModeState == LOCK_TASK_MODE_NONE + int flags1 = StatusBarManager.DISABLE_NONE; + int flags2 = StatusBarManager.DISABLE2_NONE; + + if (lockTaskModeState == LOCK_TASK_MODE_PINNED) { + flags1 = STATUS_BAR_MASK_PINNED; + + } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) { + int lockTaskFeatures = getLockTaskFeaturesForUser(userId); + Pair<Integer, Integer> statusBarFlags = getStatusBarDisableFlags(lockTaskFeatures); + flags1 = statusBarFlags.first; + flags2 = statusBarFlags.second; + } + + try { + statusBar.disable(flags1, mToken, mContext.getPackageName()); + statusBar.disable2(flags2, mToken, mContext.getPackageName()); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set status bar flags", e); + } + } + + /** + * Helper method for configuring the keyguard disabled state. + * Should only be called on the handler thread to avoid race. + */ + private void setKeyguardState(int lockTaskModeState, int userId) { + if (lockTaskModeState == LOCK_TASK_MODE_NONE) { + mWindowManager.reenableKeyguard(mToken); + + } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) { + int lockTaskFeatures = getLockTaskFeaturesForUser(userId); + if ((DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD & lockTaskFeatures) == 0) { + mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); + } else { + mWindowManager.reenableKeyguard(mToken); + } + + } else { // lockTaskModeState == LOCK_TASK_MODE_PINNED + mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG); + } + } + + /** + * Helper method for locking the device immediately. This may be necessary when the device + * leaves the pinned mode. + */ + private void lockKeyguardIfNeeded() { + try { + boolean shouldLockKeyguard = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, + USER_CURRENT) != 0; + if (shouldLockKeyguard) { + mWindowManager.lockNow(null); + mWindowManager.dismissKeyguard(null /* callback */); + getLockPatternUtils().requireCredentialEntry(USER_ALL); + } + } catch (Settings.SettingNotFoundException e) { + // No setting, don't lock. + } + } + + /** + * Translates from LockTask feature flags to StatusBarManager disable and disable2 flags. + * @param lockTaskFlags Bitfield of flags as per + * {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)} + * @return A {@link Pair} of {@link StatusBarManager#disable(int)} and + * {@link StatusBarManager#disable2(int)} flags + */ + @VisibleForTesting + Pair<Integer, Integer> getStatusBarDisableFlags(int lockTaskFlags) { + // Everything is disabled by default + int flags1 = StatusBarManager.DISABLE_MASK; + int flags2 = StatusBarManager.DISABLE2_MASK; + for (int i = STATUS_BAR_FLAG_MAP_LOCKED.size() - 1; i >= 0; i--) { + Pair<Integer, Integer> statusBarFlags = STATUS_BAR_FLAG_MAP_LOCKED.valueAt(i); + if ((STATUS_BAR_FLAG_MAP_LOCKED.keyAt(i) & lockTaskFlags) != 0) { + flags1 &= ~statusBarFlags.first; + flags2 &= ~statusBarFlags.second; + } + } + // Some flags are not used for LockTask purposes, so we mask them + flags1 &= STATUS_BAR_MASK_LOCKED; + return new Pair<>(flags1, flags2); + } + + /** + * Gets the cached value of LockTask feature flags for a specific user. + */ + private int getLockTaskFeaturesForUser(int userId) { + return mLockTaskFeatures.get(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE); + } + + /** * @return the topmost locked task */ private TaskRecord getLockedTask() { diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java index ed3f5033..0b9e0a23 100644 --- a/com/android/server/am/RecentTasks.java +++ b/com/android/server/am/RecentTasks.java @@ -37,8 +37,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NA import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; -import com.google.android.collect.Sets; - import android.app.ActivityManager; import android.app.AppGlobals; import android.content.ComponentName; @@ -58,15 +56,16 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArraySet; -import android.util.MutableBoolean; -import android.util.MutableInt; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.am.ActivityStack.ActivityState; +import com.android.server.am.TaskRecord.TaskActivitiesReport; + +import com.google.android.collect.Sets; import java.io.File; import java.io.PrintWriter; @@ -75,7 +74,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -126,6 +124,13 @@ class RecentTasks { private final UserController mUserController; /** + * Keeps track of the static recents package/component which is granted additional permissions + * to call recents-related APIs. + */ + private int mRecentsUid = -1; + private ComponentName mRecentsComponent = null; + + /** * Mapping of user id -> whether recent tasks have been loaded for that user. */ private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray( @@ -154,6 +159,7 @@ class RecentTasks { private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>(); private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>(); private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray(); + private final TaskActivitiesReport mTmpReport = new TaskActivitiesReport(); @VisibleForTesting RecentTasks(ActivityManagerService service, TaskPersister taskPersister, @@ -173,7 +179,7 @@ class RecentTasks { mTaskPersister = new TaskPersister(systemDir, stackSupervisor, service, this); mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic(); mHasVisibleRecentTasks = res.getBoolean(com.android.internal.R.bool.config_hasRecents); - loadParametersFromResources(service.mContext.getResources()); + loadParametersFromResources(res); } @VisibleForTesting @@ -217,6 +223,47 @@ class RecentTasks { : -1; } + /** + * Loads the static recents component. This is called after the system is ready, but before + * any dependent services (like SystemUI) is started. + */ + void loadRecentsComponent(Resources res) { + final String rawRecentsComponent = res.getString( + com.android.internal.R.string.config_recentsComponentName); + if (TextUtils.isEmpty(rawRecentsComponent)) { + return; + } + + final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent); + if (cn != null) { + try { + final ApplicationInfo appInfo = AppGlobals.getPackageManager() + .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId()); + if (appInfo != null) { + mRecentsUid = appInfo.uid; + mRecentsComponent = cn; + } + } catch (RemoteException e) { + Slog.w(TAG, "Could not load application info for recents component: " + cn); + } + } + } + + /** + * @return whether the current caller has the same uid as the recents component. + */ + boolean isCallerRecents(int callingUid) { + return UserHandle.isSameApp(callingUid, mRecentsUid); + } + + /** + * @return whether the given component is the recents component and shares the same uid as the + * recents component. + */ + boolean isRecentsComponent(ComponentName cn, int uid) { + return cn.equals(mRecentsComponent) && UserHandle.isSameApp(uid, mRecentsUid); + } + void registerCallback(Callbacks callback) { mCallbacks.add(callback); } @@ -339,6 +386,7 @@ class RecentTasks { } void onSystemReadyLocked() { + loadRecentsComponent(mService.mContext.getResources()); mTasks.clear(); mTaskPersister.startPersisting(); } @@ -690,7 +738,7 @@ class RecentTasks { continue; } - ActivityManager.RecentTaskInfo rti = RecentTasks.createRecentTaskInfo(tr); + final ActivityManager.RecentTaskInfo rti = createRecentTaskInfo(tr); if (!getDetailedTasks) { rti.baseIntent.replaceExtras((Bundle)null); } @@ -1327,12 +1375,14 @@ class RecentTasks { void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) { pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)"); + pw.println("mRecentsUid=" + mRecentsUid); + pw.println("mRecentsComponent=" + mRecentsComponent); if (mTasks.isEmpty()) { return; } - final MutableBoolean printedAnything = new MutableBoolean(false); - final MutableBoolean printedHeader = new MutableBoolean(false); + boolean printedAnything = false; + boolean printedHeader = false; final int size = mTasks.size(); for (int i = 0; i < size; i++) { final TaskRecord tr = mTasks.get(i); @@ -1341,10 +1391,10 @@ class RecentTasks { continue; } - if (!printedHeader.value) { + if (!printedHeader) { pw.println(" Recent tasks:"); - printedHeader.value = true; - printedAnything.value = true; + printedHeader = true; + printedAnything = true; } pw.print(" * Recent #"); pw.print(i); pw.print(": "); pw.println(tr); @@ -1353,7 +1403,7 @@ class RecentTasks { } } - if (!printedAnything.value) { + if (!printedAnything) { pw.println(" (nothing)"); } } @@ -1361,7 +1411,7 @@ class RecentTasks { /** * Creates a new RecentTaskInfo from a TaskRecord. */ - static ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) { + ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) { // Update the task description to reflect any changes in the task stack tr.updateTaskDescription(); @@ -1387,24 +1437,10 @@ class RecentTasks { rti.resizeMode = tr.mResizeMode; rti.configuration.setTo(tr.getConfiguration()); - ActivityRecord base = null; - ActivityRecord top = null; - ActivityRecord tmp; - - for (int i = tr.mActivities.size() - 1; i >= 0; --i) { - tmp = tr.mActivities.get(i); - if (tmp.finishing) { - continue; - } - base = tmp; - if (top == null || (top.state == ActivityState.INITIALIZING)) { - top = base; - } - rti.numActivities++; - } - - rti.baseActivity = (base != null) ? base.intent.getComponent() : null; - rti.topActivity = (top != null) ? top.intent.getComponent() : null; + tr.getNumRunningActivities(mTmpReport); + rti.numActivities = mTmpReport.numActivities; + rti.baseActivity = (mTmpReport.base != null) ? mTmpReport.base.intent.getComponent() : null; + rti.topActivity = (mTmpReport.top != null) ? mTmpReport.top.intent.getComponent() : null; return rti; } diff --git a/com/android/server/am/RunningTasks.java b/com/android/server/am/RunningTasks.java new file mode 100644 index 00000000..400b03a9 --- /dev/null +++ b/com/android/server/am/RunningTasks.java @@ -0,0 +1,102 @@ +/* + * 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 static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + +import android.app.ActivityManager.RunningTaskInfo; +import android.app.WindowConfiguration.ActivityType; +import android.app.WindowConfiguration.WindowingMode; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; + +/** + * Class for resolving the set of running tasks in the system. + */ +class RunningTasks { + + // Comparator to sort by last active time (descending) + private static final Comparator<TaskRecord> LAST_ACTIVE_TIME_COMPARATOR = + (o1, o2) -> Long.signum(o2.lastActiveTime - o1.lastActiveTime); + + private final TaskRecord.TaskActivitiesReport mTmpReport = + new TaskRecord.TaskActivitiesReport(); + private final TreeSet<TaskRecord> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR); + private final ArrayList<TaskRecord> mTmpStackTasks = new ArrayList<>(); + + void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType, + @WindowingMode int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays, + int callingUid, boolean allowed) { + // For each stack on each display, add the tasks into the sorted set and then pull the first + // {@param maxNum} from the set + + // Gather all of the tasks across all of the tasks, and add them to the sorted set + mTmpSortedSet.clear(); + mTmpStackTasks.clear(); + final int numDisplays = activityDisplays.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + final ActivityDisplay display = activityDisplays.valueAt(displayNdx); + for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getChildAt(stackNdx); + stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode, + callingUid, allowed); + for (int i = mTmpStackTasks.size() - 1; i >= 0; i--) { + mTmpSortedSet.addAll(mTmpStackTasks); + } + } + } + + // Take the first {@param maxNum} tasks and create running task infos for them + final Iterator<TaskRecord> iter = mTmpSortedSet.iterator(); + while (iter.hasNext()) { + if (maxNum == 0) { + break; + } + + final TaskRecord task = iter.next(); + list.add(createRunningTaskInfo(task)); + maxNum--; + } + } + + /** + * Constructs a {@link RunningTaskInfo} from a given {@param task}. + */ + private RunningTaskInfo createRunningTaskInfo(TaskRecord task) { + task.getNumRunningActivities(mTmpReport); + + final RunningTaskInfo ci = new RunningTaskInfo(); + ci.id = task.taskId; + ci.stackId = task.getStackId(); + ci.baseActivity = mTmpReport.base.intent.getComponent(); + ci.topActivity = mTmpReport.top.intent.getComponent(); + ci.lastActiveTime = task.lastActiveTime; + ci.description = task.lastDescription; + ci.numActivities = mTmpReport.numActivities; + ci.numRunning = mTmpReport.numRunning; + ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode(); + ci.resizeMode = task.mResizeMode; + ci.configuration.setTo(task.getConfiguration()); + return ci; + } +} diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java index c4512353..1b5a1ce3 100644 --- a/com/android/server/am/TaskRecord.java +++ b/com/android/server/am/TaskRecord.java @@ -30,7 +30,9 @@ 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.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; @@ -74,7 +76,6 @@ import static com.android.server.am.proto.TaskRecordProto.MIN_WIDTH; import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY; import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY; import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE; -import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE; import static com.android.server.am.proto.TaskRecordProto.STACK_ID; import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE; @@ -111,6 +112,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.util.XmlUtils; +import com.android.server.am.ActivityStack.ActivityState; import com.android.server.wm.AppWindowContainerController; import com.android.server.wm.ConfigurationContainer; import com.android.server.wm.StackWindowController; @@ -172,7 +174,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // Current version of the task record we persist. Used to check if we need to run any upgrade // code. private static final int PERSIST_TASK_VERSION = 1; - private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail"; static final int INVALID_TASK_ID = -1; private static final int INVALID_MIN_SIZE = -1; @@ -187,13 +188,13 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi REPARENT_KEEP_STACK_AT_FRONT, REPARENT_LEAVE_STACK_IN_PLACE }) - public @interface ReparentMoveStackMode {} + @interface ReparentMoveStackMode {} // Moves the stack to the front if it was not at the front - public static final int REPARENT_MOVE_STACK_TO_FRONT = 0; + static final int REPARENT_MOVE_STACK_TO_FRONT = 0; // Only moves the stack to the front if it was focused or front most already - public static final int REPARENT_KEEP_STACK_AT_FRONT = 1; + static final int REPARENT_KEEP_STACK_AT_FRONT = 1; // Do not move the stack as a part of reparenting - public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2; + static final int REPARENT_LEAVE_STACK_IN_PLACE = 2; final int taskId; // Unique identifier for this task. String affinity; // The affinity name for this task, or null; may change identity. @@ -231,9 +232,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi private boolean mSupportsPictureInPicture; // Whether or not this task and its activities // support PiP. Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag // of the root activity. - boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize - // changes on a temporary basis. - /** Can't be put in lockTask mode. */ final static int LOCK_TASK_AUTH_DONT_LOCK = 0; /** Can enter app pinning with user approval. Can never start over existing lockTask task. */ @@ -268,12 +266,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * (positive) or back (negative). Absolute value indicates time. */ long mLastTimeMoved = System.currentTimeMillis(); - /** Indication of what to run next when task exits. */ - // TODO: Shouldn't be needed if we have things in visual order. I.e. we stop using stacks or - // have a stack per standard application type... - /*@WindowConfiguration.ActivityType*/ - private int mTaskToReturnTo = ACTIVITY_TYPE_STANDARD; - /** If original intent did not allow relinquishing task identity, save that information */ private boolean mNeverRelinquishIdentity = true; @@ -281,7 +273,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // do not want to delete the stack when the task goes empty. private boolean mReuseTask = false; - private final String mFilename; CharSequence lastDescription; // Last description captured for this item. int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent. @@ -327,8 +318,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) { mService = service; - mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + - TaskPersister.IMAGE_EXTENSION; userId = UserHandle.getUserId(info.applicationInfo.uid); taskId = _taskId; lastActiveTime = SystemClock.elapsedRealtime(); @@ -348,8 +337,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, TaskDescription _taskDescription) { mService = service; - mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + - TaskPersister.IMAGE_EXTENSION; userId = UserHandle.getUserId(info.applicationInfo.uid); taskId = _taskId; lastActiveTime = SystemClock.elapsedRealtime(); @@ -368,7 +355,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi maxRecents = Math.min(Math.max(info.maxRecents, 1), ActivityManager.getMaxAppRecentsLimitStatic()); - mTaskToReturnTo = ACTIVITY_TYPE_HOME; lastTaskDescription = _taskDescription; touchActiveTime(); mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity); @@ -385,8 +371,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight) { mService = service; - mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + - TaskPersister.IMAGE_EXTENSION; taskId = _taskId; intent = _intent; affinityIntent = _affinityIntent; @@ -401,7 +385,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi isAvailable = true; autoRemoveRecents = _autoRemoveRecents; askedCompatMode = _askedCompatMode; - mTaskToReturnTo = ACTIVITY_TYPE_HOME; userId = _userId; mUserSetupComplete = userSetupComplete; effectiveUid = _effectiveUid; @@ -707,7 +690,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) { Rect bounds = getLaunchBounds(); if (bounds == null) { - toStack.layoutTaskInStack(this, null); + mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null); bounds = mBounds; } kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume); @@ -904,29 +887,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return this.intent.filterEquals(intent); } - void setTaskToReturnTo(/*@WindowConfiguration.ActivityType*/ int taskToReturnTo) { - mTaskToReturnTo = taskToReturnTo == ACTIVITY_TYPE_RECENTS - ? ACTIVITY_TYPE_HOME : taskToReturnTo; - } - - void setTaskToReturnTo(ActivityRecord source) { - if (source.isActivityTypeRecents()) { - setTaskToReturnTo(ACTIVITY_TYPE_RECENTS); - } else if (source.isActivityTypeAssistant()) { - setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT); - } - } - - int getTaskToReturnTo() { - return mTaskToReturnTo; - } - - boolean returnsToHomeTask() { - return mTaskToReturnTo == ACTIVITY_TYPE_HOME; - } - - boolean returnsToStandardTask() { - return mTaskToReturnTo == ACTIVITY_TYPE_STANDARD; + boolean returnsToHomeStack() { + final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME; + return (intent.getFlags() & returnHomeFlags) == returnHomeFlags; } void setPrevAffiliate(TaskRecord prevAffiliate) { @@ -1098,6 +1061,36 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return null; } + /** + * Return the number of running activities, and the number of non-finishing/initializing + * activities in the provided {@param reportOut} respectively. + */ + void getNumRunningActivities(TaskActivitiesReport reportOut) { + reportOut.reset(); + for (int i = mActivities.size() - 1; i >= 0; --i) { + final ActivityRecord r = mActivities.get(i); + if (r.finishing) { + continue; + } + + reportOut.base = r; + + // Increment the total number of non-finishing activities + reportOut.numActivities++; + + if (reportOut.top == null || (reportOut.top.state == ActivityState.INITIALIZING)) { + reportOut.top = r; + // Reset the number of running activities until we hit the first non-initializing + // activity + reportOut.numRunning = 0; + } + if (r.app != null && r.app.thread != null) { + // Increment the number of actually running activities + reportOut.numRunning++; + } + } + } + boolean okToShowLocked() { // NOTE: If {@link TaskRecord#topRunningActivityLocked} return is not null then it is // okay to show the activity when locked. @@ -1447,17 +1440,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi " mLockTaskAuth=" + lockTaskAuthToString()); } - boolean isOverHomeStack() { - return mTaskToReturnTo == ACTIVITY_TYPE_HOME; - } - - boolean isOverAssistantStack() { - return mTaskToReturnTo == ACTIVITY_TYPE_ASSISTANT; - } - private boolean isResizeable(boolean checkSupportsPip) { return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode) - || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable; + || (checkSupportsPip && mSupportsPictureInPicture)); } boolean isResizeable() { @@ -2089,7 +2074,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi if (mLastNonFullscreenBounds != null) { updateOverrideConfiguration(mLastNonFullscreenBounds); } else { - inStack.layoutTaskInStack(this, null); + mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null); } } else { updateOverrideConfiguration(inStack.mBounds); @@ -2164,13 +2149,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); } - if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() - || mTaskToReturnTo != ACTIVITY_TYPE_STANDARD || numFullscreen != 0) { + if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() || numFullscreen != 0) { pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents); pw.print(" isPersistable="); pw.print(isPersistable); pw.print(" numFullscreen="); pw.print(numFullscreen); - pw.print(" activityType="); pw.print(getActivityType()); - pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo); + pw.print(" activityType="); pw.println(getActivityType()); } if (rootWasReset || mNeverRelinquishIdentity || mReuseTask || mLockTaskAuth != LOCK_TASK_AUTH_PINNABLE) { @@ -2253,7 +2236,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - super.writeToProto(proto, CONFIGURATION_CONTAINER); + super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); proto.write(ID, taskId); for (int i = mActivities.size() - 1; i >= 0; i--) { ActivityRecord activity = mActivities.get(i); @@ -2270,7 +2253,6 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString()); } proto.write(ACTIVITY_TYPE, getActivityType()); - proto.write(RETURN_TO_TYPE, mTaskToReturnTo); proto.write(RESIZE_MODE, mResizeMode); proto.write(FULLSCREEN, mFullscreen); if (mBounds != null) { @@ -2280,4 +2262,19 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi proto.write(MIN_HEIGHT, mMinHeight); proto.end(token); } + + /** + * See {@link #getNumRunningActivities(TaskActivitiesReport)}. + */ + static class TaskActivitiesReport { + int numRunning; + int numActivities; + ActivityRecord top; + ActivityRecord base; + + void reset() { + numRunning = numActivities = 0; + top = base = null; + } + } } diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java index 4aa8adb9..44f83b0e 100644 --- a/com/android/server/am/UserController.java +++ b/com/android/server/am/UserController.java @@ -117,20 +117,6 @@ import java.util.concurrent.atomic.AtomicInteger; class UserController implements Handler.Callback { private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM; - /** - * Maximum number of users we allow to be running at a time, including the system user and - * its profiles. - * Note changing this to 2 is not recommended, since that would mean, if the user uses - * work profile and then switch to a secondary user, then the work profile user would be killed, - * which should work fine, but aggressively killing the work profile user that has just been - * running could cause data loss. (Even without work profile, witching from secondary user A - * to secondary user B would cause similar issues on user B.) - * - * TODO: Consider adding or replacing with "MAX_RUNNING_*SECONDARY*_USERS", which is the max - * number of running *secondary, switchable* users. - */ - static final int MAX_RUNNING_USERS = 3; - // Amount of time we wait for observers to handle a user switch before // giving up on them and unfreezing the screen. static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000; @@ -157,6 +143,17 @@ class UserController implements Handler.Callback { // when it never calls back. private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000; + /** + * Maximum number of users we allow to be running at a time, including system user. + * + * <p>This parameter only affects how many background users will be stopped when switching to a + * new user. It has no impact on {@link #startUser(int, boolean)} behavior. + * + * <p>Note: Current and system user (and their related profiles) are never stopped when + * switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers + */ + int mMaxRunningUsers; + // Lock for internal state. private final Object mLock = new Object(); @@ -245,26 +242,26 @@ class UserController implements Handler.Callback { finishUserBoot(uss); startProfiles(); synchronized (mLock) { - stopRunningUsersLU(MAX_RUNNING_USERS); + stopRunningUsersLU(mMaxRunningUsers); } } void stopRunningUsersLU(int maxRunningUsers) { - int num = mUserLru.size(); + int currentlyRunning = mUserLru.size(); int i = 0; - while (num > maxRunningUsers && i < mUserLru.size()) { + while (currentlyRunning > maxRunningUsers && i < mUserLru.size()) { Integer oldUserId = mUserLru.get(i); UserState oldUss = mStartedUsers.get(oldUserId); if (oldUss == null) { // Shouldn't happen, but be sane if it does. mUserLru.remove(i); - num--; + currentlyRunning--; continue; } if (oldUss.state == UserState.STATE_STOPPING || oldUss.state == UserState.STATE_SHUTDOWN) { // This user is already stopping, doesn't count. - num--; + currentlyRunning--; i++; continue; } @@ -272,16 +269,15 @@ class UserController implements Handler.Callback { // Owner/System user and current user can't be stopped. We count it as running // when it is not a pure system user. if (UserInfo.isSystemOnly(oldUserId)) { - num--; + currentlyRunning--; } i++; continue; } // This is a user to be stopped. - if (stopUsersLU(oldUserId, false, null) != USER_OP_SUCCESS) { - num--; + if (stopUsersLU(oldUserId, false, null) == USER_OP_SUCCESS) { + currentlyRunning--; } - num--; i++; } } @@ -814,7 +810,7 @@ class UserController implements Handler.Callback { } final int profilesToStartSize = profilesToStart.size(); int i = 0; - for (; i < profilesToStartSize && i < (MAX_RUNNING_USERS - 1); ++i) { + for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) { startUser(profilesToStart.get(i).id, /* foreground= */ false); } if (i < profilesToStartSize) { @@ -2021,7 +2017,7 @@ class UserController implements Handler.Callback { void loadUserRecents(int userId) { synchronized (mService) { - mService.mRecentTasks.loadUserRecentsLocked(userId); + mService.getRecentTasks().loadUserRecentsLocked(userId); } } diff --git a/com/android/server/appwidget/AppWidgetServiceImpl.java b/com/android/server/appwidget/AppWidgetServiceImpl.java index 76e77825..6c154389 100644 --- a/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -107,8 +107,6 @@ import com.android.server.LocalServices; import com.android.server.WidgetBackupProvider; import com.android.server.policy.IconUtilities; -import libcore.io.IoUtils; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -174,21 +172,27 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Slog.i(TAG, "Received broadcast: " + action + " on user " + userId); } - if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - onConfigurationChanged(); - } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) - || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { - synchronized (mLock) { - reloadWidgetsMaskedState(userId); - } - } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) { - String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - updateWidgetPackageSuspensionMaskedState(packages, true, getSendingUserId()); - } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) { - String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - updateWidgetPackageSuspensionMaskedState(packages, false, getSendingUserId()); - } else { - onPackageBroadcastReceived(intent, userId); + switch (action) { + case Intent.ACTION_CONFIGURATION_CHANGED: + onConfigurationChanged(); + break; + case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: + case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: + synchronized (mLock) { + reloadWidgetsMaskedState(userId); + } + break; + case Intent.ACTION_PACKAGES_SUSPENDED: + onPackageBroadcastReceived(intent, getSendingUserId()); + updateWidgetPackageSuspensionMaskedState(intent, true, getSendingUserId()); + break; + case Intent.ACTION_PACKAGES_UNSUSPENDED: + onPackageBroadcastReceived(intent, getSendingUserId()); + updateWidgetPackageSuspensionMaskedState(intent, false, getSendingUserId()); + break; + default: + onPackageBroadcastReceived(intent, getSendingUserId()); + break; } } }; @@ -378,25 +382,32 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku boolean changed = false; boolean componentsModified = false; - String pkgList[] = null; - if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = true; - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = false; - } else { - Uri uri = intent.getData(); - if (uri == null) { - return; - } - String pkgName = uri.getSchemeSpecificPart(); - if (pkgName == null) { - return; + final String pkgList[]; + switch (action) { + case Intent.ACTION_PACKAGES_SUSPENDED: + case Intent.ACTION_PACKAGES_UNSUSPENDED: + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + changed = true; + break; + case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: + added = true; + // Follow through + case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + break; + default: { + Uri uri = intent.getData(); + if (uri == null) { + return; + } + String pkgName = uri.getSchemeSpecificPart(); + if (pkgName == null) { + return; + } + pkgList = new String[] { pkgName }; + added = Intent.ACTION_PACKAGE_ADDED.equals(action); + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); } - pkgList = new String[] { pkgName }; - added = Intent.ACTION_PACKAGE_ADDED.equals(action); - changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); } if (pkgList == null || pkgList.length == 0) { return; @@ -516,12 +527,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku /** * Incrementally update the masked state due to package suspension state. */ - private void updateWidgetPackageSuspensionMaskedState(String[] packagesArray, boolean suspended, + private void updateWidgetPackageSuspensionMaskedState(Intent intent, boolean suspended, int profileId) { + String[] packagesArray = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); if (packagesArray == null) { return; } - Set<String> packages = new ArraySet<String>(Arrays.asList(packagesArray)); + Set<String> packages = new ArraySet<>(Arrays.asList(packagesArray)); synchronized (mLock) { final int N = mProviders.size(); for (int i = 0; i < N; i++) { @@ -2630,11 +2642,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // No file written for this user - nothing to do. AtomicFile file = getSavedStateFile(profileId); - try { - FileInputStream stream = file.openRead(); + try (FileInputStream stream = file.openRead()) { version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets); - IoUtils.closeQuietly(stream); - } catch (FileNotFoundException e) { + } catch (IOException e) { Slog.w(TAG, "Failed to read state: " + e); } } diff --git a/com/android/server/autofill/AutofillManagerService.java b/com/android/server/autofill/AutofillManagerService.java index 1f4161ac..6c3eb200 100644 --- a/com/android/server/autofill/AutofillManagerService.java +++ b/com/android/server/autofill/AutofillManagerService.java @@ -533,12 +533,13 @@ public final class AutofillManagerService extends SystemService { @Override public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId, Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags, - String packageName) { + ComponentName componentName) { activityToken = Preconditions.checkNotNull(activityToken, "activityToken"); appCallback = Preconditions.checkNotNull(appCallback, "appCallback"); autofillId = Preconditions.checkNotNull(autofillId, "autoFillId"); - packageName = Preconditions.checkNotNull(packageName, "packageName"); + componentName = Preconditions.checkNotNull(componentName, "componentName"); + final String packageName = Preconditions.checkNotNull(componentName.getPackageName()); Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId"); @@ -551,7 +552,7 @@ public final class AutofillManagerService extends SystemService { synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); return service.startSessionLocked(activityToken, getCallingUid(), appCallback, - autofillId, bounds, value, hasCallback, flags, packageName); + autofillId, bounds, value, hasCallback, flags, componentName); } } @@ -603,7 +604,8 @@ public final class AutofillManagerService extends SystemService { @Override public int updateOrRestartSession(IBinder activityToken, IBinder appCallback, AutofillId autoFillId, Rect bounds, AutofillValue value, int userId, - boolean hasCallback, int flags, String packageName, int sessionId, int action) { + boolean hasCallback, int flags, ComponentName componentName, int sessionId, + int action) { boolean restart = false; synchronized (mLock) { final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); @@ -614,7 +616,7 @@ public final class AutofillManagerService extends SystemService { } if (restart) { return startSession(activityToken, appCallback, autoFillId, bounds, value, userId, - hasCallback, flags, packageName); + hasCallback, flags, componentName); } // Nothing changed... diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java index 075c741e..2ed5eeee 100644 --- a/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -35,7 +35,6 @@ import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.metrics.LogMaker; import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; @@ -43,6 +42,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserManager; import android.provider.Settings; import android.service.autofill.AutofillService; @@ -58,6 +58,7 @@ import android.util.DebugUtils; import android.util.LocalLog; import android.util.Slog; import android.util.SparseArray; +import android.util.TimeUtils; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; @@ -104,6 +105,17 @@ final class AutofillManagerServiceImpl { private final LocalLog mUiLatencyHistory; /** + * Apps disabled by the service; key is package name, value is when they will be enabled again. + */ + private ArrayMap<String, Long> mDisabledApps; + + /** + * Activities disabled by the service; key is component name, value is when they will be enabled + * again. + */ + private ArrayMap<ComponentName, Long> mDisabledActivities; + + /** * Whether service was disabled for user due to {@link UserManager} restrictions. */ private boolean mDisabled; @@ -286,25 +298,46 @@ final class AutofillManagerServiceImpl { int startSessionLocked(@NonNull IBinder activityToken, int uid, @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, - int flags, @NonNull String packageName) { + int flags, @NonNull ComponentName componentName) { if (!isEnabled()) { return 0; } + + final String shortComponentName = componentName.toShortString(); + + if (isAutofillDisabledLocked(componentName)) { + if (sDebug) { + Slog.d(TAG, "startSession(" + shortComponentName + + "): ignored because disabled by service"); + } + + final IAutoFillManagerClient client = IAutoFillManagerClient.Stub + .asInterface(appCallbackToken); + try { + client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE); + } catch (RemoteException e) { + Slog.w(TAG, "Could not notify " + shortComponentName + " that it's disabled: " + e); + } + + return NO_SESSION; + } + if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags); // Occasionally clean up abandoned sessions pruneAbandonedSessionsLocked(); final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken, - hasCallback, packageName); + hasCallback, componentName); if (newSession == null) { return NO_SESSION; } final String historyItem = - "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName - + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" + - hasCallback + " f=" + flags; + "id=" + newSession.id + " uid=" + uid + " a=" + shortComponentName + + " s=" + mInfo.getServiceInfo().packageName + + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + + " hc=" + hasCallback + " f=" + flags; mRequestsHistory.log(historyItem); newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags); @@ -395,7 +428,8 @@ final class AutofillManagerServiceImpl { } private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid, - @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) { + @NonNull IBinder appCallbackToken, boolean hasCallback, + @NonNull ComponentName componentName) { // use random ids so that one app cannot know that another app creates sessions int sessionId; int tries = 0; @@ -411,7 +445,7 @@ final class AutofillManagerServiceImpl { final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock, sessionId, uid, activityToken, appCallbackToken, hasCallback, - mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName); + mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName); mSessions.put(newSession.id, newSession); return newSession; @@ -664,6 +698,46 @@ final class AutofillManagerServiceImpl { pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete); pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); + pw.print(prefix); pw.print("Disabled apps: "); + + if (mDisabledApps == null) { + pw.println("N/A"); + } else { + final int size = mDisabledApps.size(); + pw.println(size); + final StringBuilder builder = new StringBuilder(); + final long now = SystemClock.elapsedRealtime(); + for (int i = 0; i < size; i++) { + final String packageName = mDisabledApps.keyAt(i); + final long expiration = mDisabledApps.valueAt(i); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(packageName).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); + } + + pw.print(prefix); pw.print("Disabled activities: "); + + if (mDisabledActivities == null) { + pw.println("N/A"); + } else { + final int size = mDisabledActivities.size(); + pw.println(size); + final StringBuilder builder = new StringBuilder(); + final long now = SystemClock.elapsedRealtime(); + for (int i = 0; i < size; i++) { + final ComponentName component = mDisabledActivities.keyAt(i); + final long expiration = mDisabledActivities.valueAt(i); + builder.append(prefix).append(prefix) + .append(i).append(". ").append(component).append(": "); + TimeUtils.formatDuration((expiration - now), builder); + builder.append('\n'); + } + pw.println(builder); + } + final int size = mSessions.size(); if (size == 0) { pw.print(prefix); pw.println("No sessions"); @@ -764,6 +838,87 @@ final class AutofillManagerServiceImpl { return mSetupComplete && mInfo != null && !mDisabled; } + /** + * Called by {@link Session} when service asked to disable autofill for an app. + */ + void disableAutofillForApp(@NonNull String packageName, long duration) { + synchronized (mLock) { + if (mDisabledApps == null) { + mDisabledApps = new ArrayMap<>(1); + } + long expiration = SystemClock.elapsedRealtime() + duration; + // Protect it against overflow + if (expiration < 0) { + expiration = Long.MAX_VALUE; + } + mDisabledApps.put(packageName, expiration); + int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; + mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP, + packageName, getServicePackageName()) + .setCounterValue(intDuration)); + } + } + + /** + * Called by {@link Session} when service asked to disable autofill an app. + */ + void disableAutofillForActivity(@NonNull ComponentName componentName, long duration) { + synchronized (mLock) { + if (mDisabledActivities == null) { + mDisabledActivities = new ArrayMap<>(1); + } + long expiration = SystemClock.elapsedRealtime() + duration; + // Protect it against overflow + if (expiration < 0) { + expiration = Long.MAX_VALUE; + } + mDisabledActivities.put(componentName, expiration); + int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; + mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY, + componentName.getPackageName(), getServicePackageName()) + .addTaggedData(MetricsEvent.FIELD_CLASS_NAME, componentName.getClassName()) + .setCounterValue(intDuration)); + } + } + + /** + * Checks if autofill is disabled by service to the given activity. + */ + private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) { + // Check activities first. + long elapsedTime = 0; + if (mDisabledActivities != null) { + elapsedTime = SystemClock.elapsedRealtime(); + final Long expiration = mDisabledActivities.get(componentName); + if (expiration != null) { + if (expiration >= elapsedTime) return true; + // Restriction expired - clean it up. + if (sVerbose) { + Slog.v(TAG, "Removing " + componentName.toShortString() + " from disabled list"); + } + mDisabledActivities.remove(componentName); + } + } + + // Then check apps. + final String packageName = componentName.getPackageName(); + if (mDisabledApps == null) return false; + + final Long expiration = mDisabledApps.get(packageName); + if (expiration == null) return false; + + if (elapsedTime == 0) { + elapsedTime = SystemClock.elapsedRealtime(); + } + + if (expiration >= elapsedTime) return true; + + // Restriction expired - clean it up. + if (sVerbose) Slog.v(TAG, "Removing " + packageName + " from disabled list"); + mDisabledApps.remove(packageName); + return false; + } + @Override public String toString() { return "AutofillManagerServiceImpl: [userId=" + mUserId diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java index b720f74c..010995f2 100644 --- a/com/android/server/autofill/Session.java +++ b/com/android/server/autofill/Session.java @@ -54,14 +54,12 @@ import android.os.SystemClock; import android.service.autofill.AutofillService; import android.service.autofill.Dataset; import android.service.autofill.FillContext; -import android.service.autofill.FillEventHistory; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; import android.service.autofill.InternalSanitizer; import android.service.autofill.InternalValidator; import android.service.autofill.SaveInfo; import android.service.autofill.SaveRequest; -import android.service.autofill.Transformation; import android.service.autofill.ValueFinder; import android.util.ArrayMap; import android.util.ArraySet; @@ -91,7 +89,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** @@ -130,8 +127,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") @NonNull private IBinder mActivityToken; - /** Package name of the app that is auto-filled */ - @NonNull private final String mPackageName; + /** Component that's being auto-filled */ + @NonNull private final ComponentName mComponentName; @GuardedBy("mLock") private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>(); @@ -425,7 +422,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId, @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken, @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory, - @NonNull ComponentName componentName, @NonNull String packageName) { + @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName) { id = sessionId; this.uid = uid; mStartTime = SystemClock.elapsedRealtime(); @@ -433,11 +430,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mLock = lock; mUi = ui; mHandlerCaller = handlerCaller; - mRemoteFillService = new RemoteFillService(context, componentName, userId, this); + mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this); mActivityToken = activityToken; mHasCallback = hasCallback; mUiLatencyHistory = uiLatencyHistory; - mPackageName = packageName; + mComponentName = componentName; mClient = IAutoFillManagerClient.Stub.asInterface(client); writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED); @@ -483,18 +480,39 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + id + " destroyed"); return; } - } - if (response == null) { - processNullResponseLocked(requestFlags); - return; + if (response == null) { + processNullResponseLocked(requestFlags); + return; + } } mService.setLastResponse(serviceUid, id, response); - if ((response.getDatasets() == null || response.getDatasets().isEmpty()) - && response.getAuthentication() == null) { + int sessionFinishedState = 0; + final long disableDuration = response.getDisableDuration(); + if (disableDuration > 0) { + final int flags = response.getFlags(); + if (sDebug) { + final StringBuilder message = new StringBuilder("Service disabled autofill for ") + .append(mComponentName) + .append(": flags=").append(flags) + .append(", duration="); + TimeUtils.formatDuration(disableDuration, message); + Slog.d(TAG, message.toString()); + } + if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) { + mService.disableAutofillForActivity(mComponentName, disableDuration); + } else { + mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration); + } + sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE; + } + + if (((response.getDatasets() == null || response.getDatasets().isEmpty()) + && response.getAuthentication() == null) + || disableDuration > 0) { // Response is "empty" from an UI point of view, need to notify client. - notifyUnavailableToClient(false); + notifyUnavailableToClient(sessionFinishedState); } synchronized (mLock) { processResponseLocked(response, null, requestFlags); @@ -1216,7 +1234,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final IAutoFillManagerClient client = getClient(); mPendingSaveUi = new PendingUi(mActivityToken, id, client); getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(), - mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this, + mService.getServicePackageName(), saveInfo, valueFinder, + mComponentName.getPackageName(), this, mPendingSaveUi); if (client != null) { try { @@ -1620,7 +1639,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } getUiForShowing().showFillUi(filledId, response, filterText, - mService.getServicePackageName(), mPackageName, this); + mService.getServicePackageName(), mComponentName.getPackageName(), this); synchronized (mLock) { if (mUiShownTime == 0) { @@ -1660,14 +1679,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private void notifyUnavailableToClient(boolean sessionFinished) { + private void notifyUnavailableToClient(int sessionFinishedState) { synchronized (mLock) { if (mCurrentViewId == null) return; try { if (mHasCallback) { - mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinished); - } else if (sessionFinished) { - mClient.setSessionFinished(AutofillManager.STATE_FINISHED); + mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState); + } else if (sessionFinishedState != 0) { + mClient.setSessionFinished(sessionFinishedState); } } catch (RemoteException e) { Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e); @@ -1766,7 +1785,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mService.resetLastResponse(); // Nothing to be done, but need to notify client. - notifyUnavailableToClient(true); + notifyUnavailableToClient(AutofillManager.STATE_FINISHED); removeSelf(); } @@ -1964,14 +1983,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public String toString() { - return "Session: [id=" + id + ", pkg=" + mPackageName + "]"; + return "Session: [id=" + id + ", component=" + mComponentName + "]"; } void dumpLocked(String prefix, PrintWriter pw) { final String prefix2 = prefix + " "; pw.print(prefix); pw.print("id: "); pw.println(id); pw.print(prefix); pw.print("uid: "); pw.println(uid); - pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName); + pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName); pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime); pw.print(prefix); pw.print("Time to show UI: "); @@ -2201,7 +2220,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private LogMaker newLogMaker(int category, String servicePackageName) { - return Helper.newLogMaker(category, mPackageName, servicePackageName); + return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName); } private void writeLog(int category) { diff --git a/com/android/server/autofill/ui/AutoFillUI.java b/com/android/server/autofill/ui/AutoFillUI.java index 36b95fc0..dc365180 100644 --- a/com/android/server/autofill/ui/AutoFillUI.java +++ b/com/android/server/autofill/ui/AutoFillUI.java @@ -325,14 +325,14 @@ public final class AutoFillUI { } /** - * Hides all UI affordances. + * Hides all autofill UIs. */ public void hideAll(@Nullable AutoFillUiCallback callback) { mHandler.post(() -> hideAllUiThread(callback)); } /** - * Destroy all UI affordances. + * Destroy all autofill UIs. */ public void destroyAll(@Nullable PendingUi pendingSaveUi, @Nullable AutoFillUiCallback callback, boolean notifyClient) { diff --git a/com/android/server/autofill/ui/PendingUi.java b/com/android/server/autofill/ui/PendingUi.java index 0851d3bf..d1dfb5c0 100644 --- a/com/android/server/autofill/ui/PendingUi.java +++ b/com/android/server/autofill/ui/PendingUi.java @@ -21,7 +21,7 @@ import android.util.DebugUtils; import android.view.autofill.IAutoFillManagerClient; /** - * Helper class used to handle a pending Autofill affordance such as the Save UI. + * Helper class used to handle a pending Autofill UI such as the save UI. * * <p>This class is not thread safe. */ diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java index f9213aab..622b8423 100644 --- a/com/android/server/backup/BackupManagerService.java +++ b/com/android/server/backup/BackupManagerService.java @@ -2877,7 +2877,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { // The backend reports that our dataset has been wiped. Note this in // the event log; the no-success code below will reset the backup // state as well. - EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName()); + EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName); } } catch (Exception e) { Slog.e(TAG, "Error in backup thread", e); @@ -9781,7 +9781,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName); - EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName()); + String transportDirName = transport.transportDirName(); + EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName); long startRealtime = SystemClock.elapsedRealtime(); int status = transport.initializeDevice(); @@ -9794,7 +9795,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF Slog.i(TAG, "Device init successful"); int millis = (int) (SystemClock.elapsedRealtime() - startRealtime); EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); - resetBackupState(new File(mBaseStateDir, transport.transportDirName())); + resetBackupState(new File(mBaseStateDir, transportDirName)); EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis); synchronized (mQueueLock) { recordInitPendingLocked(false, transportName); diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java index 7a8a920e..c0caa557 100644 --- a/com/android/server/backup/internal/PerformBackupTask.java +++ b/com/android/server/backup/internal/PerformBackupTask.java @@ -339,7 +339,7 @@ public class PerformBackupTask implements BackupRestoreTask { // The backend reports that our dataset has been wiped. Note this in // the event log; the no-success code below will reset the backup // state as well. - EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName()); + EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName); } } catch (Exception e) { Slog.e(TAG, "Error in backup thread", e); diff --git a/com/android/server/backup/internal/PerformInitializeTask.java b/com/android/server/backup/internal/PerformInitializeTask.java index 939b1ae1..690922fd 100644 --- a/com/android/server/backup/internal/PerformInitializeTask.java +++ b/com/android/server/backup/internal/PerformInitializeTask.java @@ -79,7 +79,8 @@ public class PerformInitializeTask implements Runnable { } Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName); - EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName()); + String transportDirName = transport.transportDirName(); + EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName); long startRealtime = SystemClock.elapsedRealtime(); int status = transport.initializeDevice(); @@ -94,7 +95,7 @@ public class PerformInitializeTask implements Runnable { EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); backupManagerService .resetBackupState(new File(backupManagerService.getBaseStateDir(), - transport.transportDirName())); + transportDirName)); EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis); synchronized (backupManagerService.getQueueLock()) { backupManagerService.recordInitPendingLocked(false, transportName); diff --git a/com/android/server/connectivity/DefaultNetworkMetrics.java b/com/android/server/connectivity/DefaultNetworkMetrics.java new file mode 100644 index 00000000..8981db11 --- /dev/null +++ b/com/android/server/connectivity/DefaultNetworkMetrics.java @@ -0,0 +1,78 @@ +/* + * 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.connectivity; + +import android.net.LinkProperties; +import android.net.metrics.DefaultNetworkEvent; +import android.net.metrics.IpConnectivityLog; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks events related to the default network for the purpose of default network metrics. + * {@hide} + */ +public class DefaultNetworkMetrics { + + private static final int ROLLING_LOG_SIZE = 64; + + // Event buffer used for metrics upload. The buffer is cleared when events are collected. + @GuardedBy("this") + private final List<DefaultNetworkEvent> mEvents = new ArrayList<>(); + + public synchronized void listEvents(PrintWriter pw) { + long localTimeMs = System.currentTimeMillis(); + for (DefaultNetworkEvent ev : mEvents) { + pw.println(ev); + } + } + + public synchronized void listEventsAsProto(PrintWriter pw) { + for (DefaultNetworkEvent ev : mEvents) { + pw.print(IpConnectivityEventBuilder.toProto(ev)); + } + } + + public synchronized void flushEvents(List<IpConnectivityEvent> out) { + for (DefaultNetworkEvent ev : mEvents) { + out.add(IpConnectivityEventBuilder.toProto(ev)); + } + mEvents.clear(); + } + + public synchronized void logDefaultNetworkEvent( + NetworkAgentInfo newNai, NetworkAgentInfo prevNai) { + DefaultNetworkEvent ev = new DefaultNetworkEvent(); + if (newNai != null) { + ev.netId = newNai.network().netId; + ev.transportTypes = newNai.networkCapabilities.getTransportTypes(); + } + if (prevNai != null) { + ev.prevNetId = prevNai.network().netId; + final LinkProperties lp = prevNai.linkProperties; + ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute(); + ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute(); + } + + mEvents.add(ev); + } +} diff --git a/com/android/server/connectivity/IpConnectivityEventBuilder.java b/com/android/server/connectivity/IpConnectivityEventBuilder.java index 67e72167..3d71ecb5 100644 --- a/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -132,6 +132,18 @@ final public class IpConnectivityEventBuilder { return out; } + public static IpConnectivityEvent toProto(DefaultNetworkEvent in) { + IpConnectivityLogClass.DefaultNetworkEvent ev = + new IpConnectivityLogClass.DefaultNetworkEvent(); + ev.networkId = netIdOf(in.netId); + ev.previousNetworkId = netIdOf(in.prevNetId); + ev.transportTypes = in.transportTypes; + ev.previousNetworkIpSupport = ipSupportOf(in); + final IpConnectivityEvent out = buildEvent(in.netId, 0, null); + out.setDefaultNetworkEvent(ev); + return out; + } + private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) { final IpConnectivityEvent ev = new IpConnectivityEvent(); ev.networkId = netId; @@ -164,11 +176,6 @@ final public class IpConnectivityEventBuilder { return true; } - if (in instanceof DefaultNetworkEvent) { - setDefaultNetworkEvent(out, (DefaultNetworkEvent) in); - return true; - } - if (in instanceof NetworkEvent) { setNetworkEvent(out, (NetworkEvent) in); return true; @@ -225,16 +232,6 @@ final public class IpConnectivityEventBuilder { out.setIpReachabilityEvent(ipReachabilityEvent); } - private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) { - IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent = - new IpConnectivityLogClass.DefaultNetworkEvent(); - defaultNetworkEvent.networkId = netIdOf(in.netId); - defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId); - defaultNetworkEvent.transportTypes = in.transportTypes; - defaultNetworkEvent.previousNetworkIpSupport = ipSupportOf(in); - out.setDefaultNetworkEvent(defaultNetworkEvent); - } - private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) { IpConnectivityLogClass.NetworkEvent networkEvent = new IpConnectivityLogClass.NetworkEvent(); diff --git a/com/android/server/connectivity/IpConnectivityMetrics.java b/com/android/server/connectivity/IpConnectivityMetrics.java index f2445fa3..24217e6e 100644 --- a/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/com/android/server/connectivity/IpConnectivityMetrics.java @@ -32,12 +32,15 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Base64; import android.util.Log; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.RingBuffer; import com.android.internal.util.TokenBucket; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -112,6 +115,9 @@ final public class IpConnectivityMetrics extends SystemService { private final ToIntFunction<Context> mCapacityGetter; + @VisibleForTesting + final DefaultNetworkMetrics mDefaultNetworkMetrics = new DefaultNetworkMetrics(); + public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) { super(ctx); mCapacityGetter = capacityGetter; @@ -135,6 +141,8 @@ final public class IpConnectivityMetrics extends SystemService { publishBinderService(SERVICE_NAME, impl); publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener); + + LocalServices.addService(Logger.class, new LoggerImpl()); } } @@ -188,6 +196,8 @@ final public class IpConnectivityMetrics extends SystemService { final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events); + mDefaultNetworkMetrics.flushEvents(protoEvents); + if (mNetdListener != null) { mNetdListener.flushStatistics(protoEvents); } @@ -228,6 +238,7 @@ final public class IpConnectivityMetrics extends SystemService { if (mNetdListener != null) { mNetdListener.listAsProtos(pw); } + mDefaultNetworkMetrics.listEventsAsProto(pw); return; } @@ -237,6 +248,7 @@ final public class IpConnectivityMetrics extends SystemService { if (mNetdListener != null) { mNetdListener.list(pw); } + mDefaultNetworkMetrics.listEvents(pw); } /** @@ -254,6 +266,7 @@ final public class IpConnectivityMetrics extends SystemService { if (mNetdListener != null) { mNetdListener.list(pw); } + mDefaultNetworkMetrics.listEvents(pw); } private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -366,4 +379,15 @@ final public class IpConnectivityMetrics extends SystemService { map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50)); return map; } + + /** Direct non-Binder interface for event producer clients within the system servers. */ + public interface Logger { + DefaultNetworkMetrics defaultNetworkMetrics(); + } + + private class LoggerImpl implements Logger { + public DefaultNetworkMetrics defaultNetworkMetrics() { + return mDefaultNetworkMetrics; + } + } } diff --git a/com/android/server/connectivity/Tethering.java b/com/android/server/connectivity/Tethering.java index d7cd81ff..59870cb9 100644 --- a/com/android/server/connectivity/Tethering.java +++ b/com/android/server/connectivity/Tethering.java @@ -28,6 +28,7 @@ import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; +import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; import static com.android.server.ConnectivityService.SHORT_ARG; import android.app.Notification; @@ -60,6 +61,7 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.net.util.VersionedBroadcastListener; import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Bundle; @@ -68,6 +70,7 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.Parcel; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; @@ -184,6 +187,8 @@ public class Tethering extends BaseNetworkObserver { // TODO: Figure out how to merge this and other downstream-tracking objects // into a single coherent structure. private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams; + private final VersionedBroadcastListener mCarrierConfigChange; + // TODO: Delete SimChangeListener; it's obsolete. private final SimChangeListener mSimChange; private volatile TetheringConfiguration mConfig; @@ -224,11 +229,26 @@ public class Tethering extends BaseNetworkObserver { mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new HashSet<>(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); + mCarrierConfigChange = new VersionedBroadcastListener( + "CarrierConfigChangeListener", mContext, smHandler, filter, + (Intent ignored) -> { + mLog.log("OBSERVED carrier config change"); + reevaluateSimCardProvisioning(); + }); + // TODO: Remove SimChangeListener altogether. For now, we retain it + // for logging purposes in case we need to debug something that might + // be related to changing signals from ACTION_SIM_STATE_CHANGED to + // ACTION_CARRIER_CONFIG_CHANGED. mSimChange = new SimChangeListener( - mContext, smHandler, () -> reevaluateSimCardProvisioning()); + mContext, smHandler, () -> { + mLog.log("OBSERVED SIM card change"); + }); mStateReceiver = new StateReceiver(); - IntentFilter filter = new IntentFilter(); + filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); @@ -364,18 +384,30 @@ public class Tethering extends BaseNetworkObserver { return false; } + if (carrierConfigAffirmsEntitlementCheckNotRequired()) { + return false; + } + return (provisionApp.length == 2); + } + + // The logic here is aimed solely at confirming that a CarrierConfig exists + // and affirms that entitlement checks are not required. + // + // TODO: find a better way to express this, or alter the checking process + // entirely so that this is more intuitive. + private boolean carrierConfigAffirmsEntitlementCheckNotRequired() { // Check carrier config for entitlement checks final CarrierConfigManager configManager = (CarrierConfigManager) mContext .getSystemService(Context.CARRIER_CONFIG_SERVICE); - if (configManager != null && configManager.getConfig() != null) { - // we do have a CarrierConfigManager and it has a config. - boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean( - CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); - if (!isEntitlementCheckRequired) { - return false; - } - } - return (provisionApp.length == 2); + if (configManager == null) return false; + + final PersistableBundle carrierConfig = configManager.getConfig(); + if (carrierConfig == null) return false; + + // A CarrierConfigManager was found and it has a config. + final boolean isEntitlementCheckRequired = carrierConfig.getBoolean( + CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); + return !isEntitlementCheckRequired; } // Used by the SIM card change observation code. @@ -818,6 +850,7 @@ public class Tethering extends BaseNetworkObserver { } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { handleWifiApAction(intent); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { + mLog.log("OBSERVED configuration changed"); updateConfiguration(); } } @@ -1192,6 +1225,7 @@ public class Tethering extends BaseNetworkObserver { private void reevaluateSimCardProvisioning() { if (!hasMobileHotspotProvisionApp()) return; + if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; ArrayList<Integer> tethered = new ArrayList<>(); synchronized (mPublicSync) { @@ -1559,6 +1593,7 @@ public class Tethering extends BaseNetworkObserver { return; } + mCarrierConfigChange.startListening(); mSimChange.startListening(); mUpstreamNetworkMonitor.start(); @@ -1576,6 +1611,7 @@ public class Tethering extends BaseNetworkObserver { mOffload.stop(); mUpstreamNetworkMonitor.stop(); mSimChange.stopListening(); + mCarrierConfigChange.stopListening(); notifyDownstreamsOfNewUpstreamIface(null); handleNewUpstreamNetworkState(null); } diff --git a/com/android/server/connectivity/tethering/SimChangeListener.java b/com/android/server/connectivity/tethering/SimChangeListener.java index 3e60f9f6..33c9355a 100644 --- a/com/android/server/connectivity/tethering/SimChangeListener.java +++ b/com/android/server/connectivity/tethering/SimChangeListener.java @@ -23,12 +23,15 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.util.VersionedBroadcastListener; +import android.net.util.VersionedBroadcastListener.IntentCallback; import android.os.Handler; import android.util.Log; import com.android.internal.telephony.TelephonyIntents; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; /** @@ -37,88 +40,40 @@ import java.util.concurrent.atomic.AtomicInteger; * * @hide */ -public class SimChangeListener { +public class SimChangeListener extends VersionedBroadcastListener { private static final String TAG = SimChangeListener.class.getSimpleName(); private static final boolean DBG = false; - private final Context mContext; - private final Handler mTarget; - private final AtomicInteger mSimBcastGenerationNumber; - private final Runnable mCallback; - private BroadcastReceiver mBroadcastReceiver; - public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) { - mContext = ctx; - mTarget = handler; - mCallback = onSimCardLoadedCallback; - mSimBcastGenerationNumber = new AtomicInteger(0); - } - - public int generationNumber() { - return mSimBcastGenerationNumber.get(); + super(TAG, ctx, handler, makeIntentFilter(), makeCallback(onSimCardLoadedCallback)); } - public void startListening() { - if (DBG) Log.d(TAG, "startListening for SIM changes"); - - if (mBroadcastReceiver != null) return; - - mBroadcastReceiver = new SimChangeBroadcastReceiver( - mSimBcastGenerationNumber.incrementAndGet()); + private static IntentFilter makeIntentFilter() { final IntentFilter filter = new IntentFilter(); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); - - mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget); + return filter; } - public void stopListening() { - if (DBG) Log.d(TAG, "stopListening for SIM changes"); - - if (mBroadcastReceiver == null) return; - - mSimBcastGenerationNumber.incrementAndGet(); - mContext.unregisterReceiver(mBroadcastReceiver); - mBroadcastReceiver = null; - } - - private boolean isSimCardLoaded(String state) { - return INTENT_VALUE_ICC_LOADED.equals(state); - } - - private class SimChangeBroadcastReceiver extends BroadcastReceiver { - // used to verify this receiver is still current - final private int mGenerationNumber; - - // used to check the sim state transition from non-loaded to loaded - private boolean mSimNotLoadedSeen = false; - - public SimChangeBroadcastReceiver(int generationNumber) { - mGenerationNumber = generationNumber; - } - - @Override - public void onReceive(Context context, Intent intent) { - final int currentGenerationNumber = mSimBcastGenerationNumber.get(); - - if (DBG) { - Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber + - ", current generationNumber=" + currentGenerationNumber); - } - if (mGenerationNumber != currentGenerationNumber) return; - - final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE); - Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" + - mSimNotLoadedSeen); - - if (!isSimCardLoaded(state)) { - mSimNotLoadedSeen = true; - return; - } - - if (mSimNotLoadedSeen) { - mSimNotLoadedSeen = false; - mCallback.run(); + private static Consumer<Intent> makeCallback(Runnable onSimCardLoadedCallback) { + return new Consumer<Intent>() { + private boolean mSimNotLoadedSeen = false; + + @Override + public void accept(Intent intent) { + final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE); + Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" + + mSimNotLoadedSeen); + + if (!INTENT_VALUE_ICC_LOADED.equals(state)) { + mSimNotLoadedSeen = true; + return; + } + + if (mSimNotLoadedSeen) { + mSimNotLoadedSeen = false; + onSimCardLoadedCallback.run(); + } } - } + }; } } diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java index 80f6a4b9..2d8a0ee0 100644 --- a/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -18,6 +18,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED; import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; @@ -225,6 +226,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component"; + private static final String TAG_LOCK_TASK_FEATURES = "lock-task-features"; + private static final String TAG_STATUS_BAR = "statusbar"; private static final String ATTR_DISABLED = "disabled"; @@ -507,6 +510,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // This is the list of component allowed to start lock task mode. List<String> mLockTaskPackages = new ArrayList<>(); + // Bitfield of feature flags to be enabled during LockTask mode. + int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE; + boolean mStatusBarDisabled = false; ComponentName mRestrictionsProvider; @@ -2628,6 +2634,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, TAG_LOCK_TASK_COMPONENTS); } + if (policy.mLockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) { + out.startTag(null, TAG_LOCK_TASK_FEATURES); + out.attribute(null, ATTR_VALUE, Integer.toString(policy.mLockTaskFeatures)); + out.endTag(null, TAG_LOCK_TASK_FEATURES); + } + if (policy.mStatusBarDisabled) { out.startTag(null, TAG_STATUS_BAR); out.attribute(null, ATTR_DISABLED, Boolean.toString(policy.mStatusBarDisabled)); @@ -2868,6 +2880,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME)); } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) { policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name")); + } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) { + policy.mLockTaskFeatures = Integer.parseInt( + parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_STATUS_BAR.equals(tag)) { policy.mStatusBarDisabled = Boolean.parseBoolean( parser.getAttributeValue(null, ATTR_DISABLED)); @@ -2936,6 +2951,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { validatePasswordOwnerLocked(policy); updateMaximumTimeToLockLocked(userHandle); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle); + updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle); if (policy.mStatusBarDisabled) { setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle); } @@ -2953,6 +2969,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void updateLockTaskFeaturesLocked(int flags, int userId) { + long ident = mInjector.binderClearCallingIdentity(); + try { + mInjector.getIActivityManager() + .updateLockTaskFeatures(userId, flags); + } catch (RemoteException e) { + // Not gonna happen. + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + private void updateDeviceOwnerLocked() { long ident = mInjector.binderClearCallingIdentity(); try { @@ -6939,6 +6967,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED; policy.mAffiliationIds.clear(); policy.mLockTaskPackages.clear(); + policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE; saveSettingsLocked(userId); try { @@ -8921,25 +8950,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { updateLockTaskPackagesLocked(packages, userHandle); } - private void maybeClearLockTaskPackagesLocked() { - final long ident = mInjector.binderClearCallingIdentity(); - try { - final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true); - for (int i = 0; i < userInfos.size(); i++) { - int userId = userInfos.get(i).id; - final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages; - if (!lockTaskPackages.isEmpty() && - !isUserAffiliatedWithDeviceLocked(userId)) { - Slog.d(LOG_TAG, - "User id " + userId + " not affiliated. Clearing lock task packages"); - setLockTaskPackagesLocked(userId, Collections.<String>emptyList()); - } - } - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } - } - @Override public String[] getLockTaskPackages(ComponentName who) { Preconditions.checkNotNull(who, "ComponentName is null"); @@ -8966,12 +8976,82 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public void setLockTaskFeatures(ComponentName who, int flags) { + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = mInjector.userHandleGetCallingUserId(); + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (!isUserAffiliatedWithDeviceLocked(userHandle)) { + throw new SecurityException("Admin " + who + + " is neither the device owner or affiliated user's profile owner."); + } + setLockTaskFeaturesLocked(userHandle, flags); + } + } + + private void setLockTaskFeaturesLocked(int userHandle, int flags) { + DevicePolicyData policy = getUserData(userHandle); + policy.mLockTaskFeatures = flags; + saveSettingsLocked(userHandle); + updateLockTaskFeaturesLocked(flags, userHandle); + } + + @Override + public int getLockTaskFeatures(ComponentName who) { + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = mInjector.userHandleGetCallingUserId(); + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (!isUserAffiliatedWithDeviceLocked(userHandle)) { + throw new SecurityException("Admin " + who + + " is neither the device owner or affiliated user's profile owner."); + } + return getUserData(userHandle).mLockTaskFeatures; + } + } + + private void maybeClearLockTaskPolicyLocked() { + final long ident = mInjector.binderClearCallingIdentity(); + try { + final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true); + for (int i = userInfos.size() - 1; i >= 0; i--) { + int userId = userInfos.get(i).id; + if (isUserAffiliatedWithDeviceLocked(userId)) { + continue; + } + + final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages; + if (!lockTaskPackages.isEmpty()) { + Slog.d(LOG_TAG, + "User id " + userId + " not affiliated. Clearing lock task packages"); + setLockTaskPackagesLocked(userId, Collections.<String>emptyList()); + } + final int lockTaskFeatures = getUserData(userId).mLockTaskFeatures; + if (lockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE){ + Slog.d(LOG_TAG, + "User id " + userId + " not affiliated. Clearing lock task features"); + setLockTaskFeaturesLocked(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE); + } + } + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + + @Override public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) { if (!isCallerWithSystemUid()) { throw new SecurityException("notifyLockTaskModeChanged can only be called by system"); } synchronized (this) { final DevicePolicyData policy = getUserData(userHandle); + + if (policy.mStatusBarDisabled) { + // Status bar is managed by LockTaskController during LockTask, so we cancel this + // policy when LockTask starts, and reapply it when LockTask ends + setStatusBarDisabledInternal(!isEnabled, userHandle); + } + Bundle adminExtras = new Bundle(); adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg); for (ActiveAdmin admin : policy.mAdminList) { @@ -9182,8 +9262,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); DevicePolicyData policy = getUserData(userId); if (policy.mStatusBarDisabled != disabled) { - if (!setStatusBarDisabledInternal(disabled, userId)) { - return false; + boolean isLockTaskMode = false; + try { + isLockTaskMode = mInjector.getIActivityManager().getLockTaskModeState() + != LOCK_TASK_MODE_NONE; + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Failed to get LockTask mode"); + } + if (!isLockTaskMode) { + if (!setStatusBarDisabledInternal(disabled, userId)) { + return false; + } } policy.mStatusBarDisabled = disabled; saveSettingsLocked(userId); @@ -10283,7 +10372,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // but as a result of that other users might become affiliated or un-affiliated. maybePauseDeviceWideLoggingLocked(); maybeResumeDeviceWideLoggingLocked(); - maybeClearLockTaskPackagesLocked(); + maybeClearLockTaskPolicyLocked(); } } diff --git a/com/android/server/display/ColorFade.java b/com/android/server/display/ColorFade.java index 2541050f..c2167eb8 100644 --- a/com/android/server/display/ColorFade.java +++ b/com/android/server/display/ColorFade.java @@ -585,9 +585,11 @@ final class ColorFade { } else { flags = SurfaceControl.OPAQUE | SurfaceControl.HIDDEN; } - mSurfaceControl = new SurfaceControl(mSurfaceSession, - "ColorFade", mDisplayWidth, mDisplayHeight, - PixelFormat.OPAQUE, flags); + mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) + .setName("ColorFade") + .setSize(mDisplayWidth, mDisplayHeight) + .setFlags(flags) + .build(); } catch (OutOfResourcesException ex) { Slog.e(TAG, "Unable to create surface.", ex); return false; diff --git a/com/android/server/display/DisplayTransformManager.java b/com/android/server/display/DisplayTransformManager.java index dbbb318d..bef6898a 100644 --- a/com/android/server/display/DisplayTransformManager.java +++ b/com/android/server/display/DisplayTransformManager.java @@ -16,15 +16,20 @@ package com.android.server.display; +import android.app.ActivityManager; +import android.app.IActivityManager; import android.opengl.Matrix; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.NightDisplayController; import java.util.Arrays; /** @@ -34,6 +39,8 @@ public class DisplayTransformManager { private static final String TAG = "DisplayTransformManager"; + private static final String SURFACE_FLINGER = "SurfaceFlinger"; + /** * Color transform level used by Night display to tint the display red. */ @@ -50,6 +57,15 @@ public class DisplayTransformManager { private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015; private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014; + private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation"; + private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode"; + + private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022; + private static final int SURFACE_FLINGER_TRANSACTION_NATIVE_MODE = 1023; + + private static final float COLOR_SATURATION_NATURAL = 1.0f; + private static final float COLOR_SATURATION_BOOSTED = 1.1f; + /** * Map of level -> color transformation matrix. */ @@ -161,7 +177,7 @@ public class DisplayTransformManager { * Propagates the provided color transformation matrix to the SurfaceFlinger. */ private static void applyColorMatrix(float[] m) { - final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); + final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); if (flinger != null) { final Parcel data = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); @@ -187,7 +203,7 @@ public class DisplayTransformManager { * Propagates the provided Daltonization mode to the SurfaceFlinger. */ private static void applyDaltonizerMode(int mode) { - final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); + final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); if (flinger != null) { final Parcel data = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); @@ -201,4 +217,73 @@ public class DisplayTransformManager { } } } + + public static boolean isNativeModeEnabled() { + return SystemProperties.getBoolean(PERSISTENT_PROPERTY_NATIVE_MODE, false); + } + + public boolean setColorMode(int colorMode) { + if (colorMode == NightDisplayController.COLOR_MODE_NATURAL) { + applySaturation(COLOR_SATURATION_NATURAL); + setNativeMode(false); + } else if (colorMode == NightDisplayController.COLOR_MODE_BOOSTED) { + applySaturation(COLOR_SATURATION_BOOSTED); + setNativeMode(false); + } else if (colorMode == NightDisplayController.COLOR_MODE_SATURATED) { + applySaturation(COLOR_SATURATION_NATURAL); + setNativeMode(true); + } + + updateConfiguration(); + + return true; + } + + /** + * Propagates the provided saturation to the SurfaceFlinger. + */ + private void applySaturation(float saturation) { + SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation)); + final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); + if (flinger != null) { + final Parcel data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + data.writeFloat(saturation); + try { + flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to set saturation", ex); + } finally { + data.recycle(); + } + } + } + + /** + * Toggles native mode on/off in SurfaceFlinger. + */ + private void setNativeMode(boolean enabled) { + SystemProperties.set(PERSISTENT_PROPERTY_NATIVE_MODE, enabled ? "1" : "0"); + final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER); + if (flinger != null) { + final Parcel data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + data.writeInt(enabled ? 1 : 0); + try { + flinger.transact(SURFACE_FLINGER_TRANSACTION_NATIVE_MODE, data, null, 0); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to set native mode", ex); + } finally { + data.recycle(); + } + } + } + + private void updateConfiguration() { + try { + ActivityManager.getService().updateConfiguration(null); + } catch (RemoteException e) { + Log.e(TAG, "Could not update configuration", e); + } + } } diff --git a/com/android/server/display/NightDisplayService.java b/com/android/server/display/NightDisplayService.java index 9cf13672..a7c3ff9e 100644 --- a/com/android/server/display/NightDisplayService.java +++ b/com/android/server/display/NightDisplayService.java @@ -52,7 +52,8 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.TimeZone; + +import com.android.internal.R; import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; @@ -126,12 +127,6 @@ public final class NightDisplayService extends SystemService public NightDisplayService(Context context) { super(context); mHandler = new Handler(Looper.getMainLooper()); - - final String[] coefficients = context.getResources().getStringArray( - com.android.internal.R.array.config_nightDisplayColorTemperatureCoefficients); - for (int i = 0; i < 9 && i < coefficients.length; i++) { - mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]); - } } @Override @@ -236,6 +231,8 @@ public final class NightDisplayService extends SystemService mController = new NightDisplayController(getContext(), mCurrentUser); mController.setListener(this); + setCoefficientMatrix(getContext()); + // Prepare color transformation matrix. setMatrix(mController.getColorTemperature(), mMatrixNight); @@ -331,6 +328,26 @@ public final class NightDisplayService extends SystemService applyTint(true); } + @Override + public void onDisplayColorModeChanged(int colorMode) { + final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class); + dtm.setColorMode(colorMode); + + setCoefficientMatrix(getContext()); + setMatrix(mController.getColorTemperature(), mMatrixNight); + applyTint(true); + } + + private void setCoefficientMatrix(Context context) { + final boolean isNative = DisplayTransformManager.isNativeModeEnabled(); + final String[] coefficients = context.getResources().getStringArray(isNative + ? R.array.config_nightDisplayColorTemperatureCoefficientsNative + : R.array.config_nightDisplayColorTemperatureCoefficients); + for (int i = 0; i < 9 && i < coefficients.length; i++) { + mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]); + } + } + /** * Applies current color temperature matrix, or removes it if deactivated. * diff --git a/com/android/server/fingerprint/FingerprintService.java b/com/android/server/fingerprint/FingerprintService.java index 1df9c861..d0d951b8 100644 --- a/com/android/server/fingerprint/FingerprintService.java +++ b/com/android/server/fingerprint/FingerprintService.java @@ -59,9 +59,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.security.KeyStore; -import android.service.fingerprint.FingerprintActionStatsProto; -import android.service.fingerprint.FingerprintServiceDumpProto; -import android.service.fingerprint.FingerprintUserStatsProto; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; @@ -1374,11 +1371,11 @@ public class FingerprintService extends SystemService implements IHwBinder.Death final PerformanceStats normal = mPerformanceMap.get(userId); if (normal != null) { final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL); - proto.write(FingerprintActionStatsProto.ACCEPT, normal.accept); - proto.write(FingerprintActionStatsProto.REJECT, normal.reject); - proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire); - proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout); - proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.permanentLockout); + proto.write(PerformanceStatsProto.ACCEPT, normal.accept); + proto.write(PerformanceStatsProto.REJECT, normal.reject); + proto.write(PerformanceStatsProto.ACQUIRE, normal.acquire); + proto.write(PerformanceStatsProto.LOCKOUT, normal.lockout); + proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, normal.permanentLockout); proto.end(countsToken); } @@ -1387,11 +1384,11 @@ public class FingerprintService extends SystemService implements IHwBinder.Death final PerformanceStats crypto = mCryptoPerformanceMap.get(userId); if (crypto != null) { final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO); - proto.write(FingerprintActionStatsProto.ACCEPT, crypto.accept); - proto.write(FingerprintActionStatsProto.REJECT, crypto.reject); - proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire); - proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout); - proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.permanentLockout); + proto.write(PerformanceStatsProto.ACCEPT, crypto.accept); + proto.write(PerformanceStatsProto.REJECT, crypto.reject); + proto.write(PerformanceStatsProto.ACQUIRE, crypto.acquire); + proto.write(PerformanceStatsProto.LOCKOUT, crypto.lockout); + proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, crypto.permanentLockout); proto.end(countsToken); } diff --git a/com/android/server/job/JobServiceContext.java b/com/android/server/job/JobServiceContext.java index d3fd3a99..a7e674b5 100644 --- a/com/android/server/job/JobServiceContext.java +++ b/com/android/server/job/JobServiceContext.java @@ -65,7 +65,7 @@ public final class JobServiceContext implements ServiceConnection { private static final boolean DEBUG = JobSchedulerService.DEBUG; private static final String TAG = "JobServiceContext"; /** Amount of time a job is allowed to execute for before being considered timed-out. */ - private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins. + public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins. /** Amount of time the JobScheduler waits for the initial service launch+bind. */ private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000; /** Amount of time the JobScheduler will wait for a response from an app for a message. */ @@ -216,7 +216,7 @@ public final class JobServiceContext implements ServiceConnection { final JobInfo ji = job.getJob(); mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(), ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(), - isDeadlineExpired, triggeredUris, triggeredAuthorities); + isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network); mExecutionStartTimeElapsed = SystemClock.elapsedRealtime(); // Once we'e begun executing a job, we by definition no longer care whether diff --git a/com/android/server/job/controllers/ConnectivityController.java b/com/android/server/job/controllers/ConnectivityController.java index 78367fe9..ddee345a 100644 --- a/com/android/server/job/controllers/ConnectivityController.java +++ b/com/android/server/job/controllers/ConnectivityController.java @@ -25,13 +25,16 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkPolicyManager; +import android.net.TrafficStats; import android.os.Process; import android.os.UserHandle; +import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.job.JobSchedulerService; +import com.android.server.job.JobServiceContext; import com.android.server.job.StateChangedListener; import java.io.PrintWriter; @@ -99,18 +102,66 @@ public final class ConnectivityController extends StateController implements } } + /** + * Test to see if running the given job on the given network is sane. + * <p> + * For example, if a job is trying to send 10MB over a 128Kbps EDGE + * connection, it would take 10.4 minutes, and has no chance of succeeding + * before the job times out, so we'd be insane to try running it. + */ + private boolean isSane(JobStatus jobStatus, NetworkCapabilities capabilities) { + final long estimatedBytes = jobStatus.getEstimatedNetworkBytes(); + if (estimatedBytes == JobInfo.NETWORK_BYTES_UNKNOWN) { + // We don't know how large the job is; cross our fingers! + return true; + } + if (capabilities == null) { + // We don't know what the network is like; cross our fingers! + return true; + } + + // We don't ask developers to differentiate between upstream/downstream + // in their size estimates, so test against the slowest link direction. + final long downstream = capabilities.getLinkDownstreamBandwidthKbps(); + final long upstream = capabilities.getLinkUpstreamBandwidthKbps(); + final long slowest; + if (downstream > 0 && upstream > 0) { + slowest = Math.min(downstream, upstream); + } else if (downstream > 0) { + slowest = downstream; + } else if (upstream > 0) { + slowest = upstream; + } else { + // We don't know what the network is like; cross our fingers! + return true; + } + + final long estimatedMillis = ((estimatedBytes * DateUtils.SECOND_IN_MILLIS) + / (slowest * TrafficStats.KB_IN_BYTES / 8)); + if (estimatedMillis > JobServiceContext.EXECUTING_TIMESLICE_MILLIS) { + // If we'd never finish before the timeout, we'd be insane! + Slog.w(TAG, "Estimated " + estimatedBytes + " bytes over " + slowest + + " kbps network would take " + estimatedMillis + "ms; that's insane!"); + return false; + } else { + return true; + } + } + private boolean updateConstraintsSatisfied(JobStatus jobStatus) { final int jobUid = jobStatus.getSourceUid(); final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0; - final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobUid, ignoreBlocked); final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked); + final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked); + final NetworkCapabilities capabilities = (network != null) ? mConnManager.getNetworkCapabilities(network) : null; + final boolean connected = (info != null) && info.isConnected(); final boolean validated = (capabilities != null) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); - final boolean connected = (info != null) && info.isConnected(); - final boolean connectionUsable = connected && validated; + final boolean sane = isSane(jobStatus, capabilities); + final boolean connectionUsable = connected && validated && sane; final boolean metered = connected && (capabilities != null) && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); @@ -125,6 +176,11 @@ public final class ConnectivityController extends StateController implements changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered); changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming); + // Pass along the evaluated network for job to use; prevents race + // conditions as default routes change over time, and opens the door to + // using non-default routes. + jobStatus.network = network; + // Track system-uid connected/validated as a general reportable proxy for the // overall state of connectivity constraint satisfiability. if (jobUid == Process.SYSTEM_UID) { diff --git a/com/android/server/job/controllers/JobStatus.java b/com/android/server/job/controllers/JobStatus.java index 23caa8cf..46ed84e5 100644 --- a/com/android/server/job/controllers/JobStatus.java +++ b/com/android/server/job/controllers/JobStatus.java @@ -22,6 +22,7 @@ import android.app.job.JobInfo; import android.app.job.JobWorkItem; import android.content.ClipData; import android.content.ComponentName; +import android.net.Network; import android.net.Uri; import android.os.RemoteException; import android.os.SystemClock; @@ -167,6 +168,7 @@ public final class JobStatus { // These are filled in by controllers when preparing for execution. public ArraySet<Uri> changedUris; public ArraySet<String> changedAuthorities; + public Network network; public int lastEvaluatedPriority; @@ -217,6 +219,8 @@ public final class JobStatus { */ ContentObserverController.JobInstance contentObserverJobInstance; + private long totalNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN; + /** Provide a handle to the service that this job will be run on. */ public int getServiceToken() { return callingUid; @@ -294,6 +298,8 @@ public final class JobStatus { mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; + + updateEstimatedNetworkBytesLocked(); } /** Copy constructor: used specifically when cloning JobStatus objects for persistence, @@ -387,6 +393,7 @@ public final class JobStatus { sourcePackageName, sourceUserId, toShortString())); } pendingWork.add(work); + updateEstimatedNetworkBytesLocked(); } public JobWorkItem dequeueWorkLocked() { @@ -399,6 +406,7 @@ public final class JobStatus { executingWork.add(work); work.bumpDeliveryCount(); } + updateEstimatedNetworkBytesLocked(); return work; } return null; @@ -457,6 +465,7 @@ public final class JobStatus { pendingWork = null; executingWork = null; incomingJob.nextPendingWorkId = nextPendingWorkId; + incomingJob.updateEstimatedNetworkBytesLocked(); } else { // We are completely stopping the job... need to clean up work. ungrantWorkList(am, pendingWork); @@ -464,6 +473,7 @@ public final class JobStatus { ungrantWorkList(am, executingWork); executingWork = null; } + updateEstimatedNetworkBytesLocked(); } public void prepareLocked(IActivityManager am) { @@ -566,6 +576,38 @@ public final class JobStatus { return job.getFlags(); } + private void updateEstimatedNetworkBytesLocked() { + totalNetworkBytes = computeEstimatedNetworkBytesLocked(); + } + + private long computeEstimatedNetworkBytesLocked() { + // If any component of the job has unknown usage, we don't have a + // complete picture of what data will be used, and we have to treat the + // entire job as unknown. + long totalNetworkBytes = 0; + long networkBytes = job.getEstimatedNetworkBytes(); + if (networkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) { + return JobInfo.NETWORK_BYTES_UNKNOWN; + } else { + totalNetworkBytes += networkBytes; + } + if (pendingWork != null) { + for (int i = 0; i < pendingWork.size(); i++) { + networkBytes = pendingWork.get(i).getEstimatedNetworkBytes(); + if (networkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) { + return JobInfo.NETWORK_BYTES_UNKNOWN; + } else { + totalNetworkBytes += networkBytes; + } + } + } + return totalNetworkBytes; + } + + public long getEstimatedNetworkBytes() { + return totalNetworkBytes; + } + /** Does this job have any sort of networking constraint? */ public boolean hasConnectivityConstraint() { return (requiredConstraints&CONNECTIVITY_MASK) != 0; @@ -1045,6 +1087,9 @@ public final class JobStatus { if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) { pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType()); } + if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { + pw.print(prefix); pw.print(" Network bytes: "); pw.println(totalNetworkBytes); + } if (job.getMinLatencyMillis() != 0) { pw.print(prefix); pw.print(" Minimum latency: "); TimeUtils.formatDuration(job.getMinLatencyMillis(), pw); @@ -1101,6 +1146,9 @@ public final class JobStatus { } } } + if (network != null) { + pw.print(prefix); pw.print("Network: "); pw.println(network); + } if (pendingWork != null && pendingWork.size() > 0) { pw.print(prefix); pw.println("Pending work:"); for (int i = 0; i < pendingWork.size(); i++) { diff --git a/com/android/server/location/GnssLocationProvider.java b/com/android/server/location/GnssLocationProvider.java index e41c17df..4cf35bc4 100644 --- a/com/android/server/location/GnssLocationProvider.java +++ b/com/android/server/location/GnssLocationProvider.java @@ -669,9 +669,11 @@ public class GnssLocationProvider implements LocationProviderInterface { for (String item : configValues) { if (DEBUG) Log.d(TAG, "GpsParamsResource: " + item); // We need to support "KEY =", but not "=VALUE". - String[] split = item.split("="); - if (split.length == 2) { - properties.setProperty(split[0].trim().toUpperCase(), split[1]); + int index = item.indexOf("="); + if (index > 0 && index + 1 < item.length()) { + String key = item.substring(0, index); + String value = item.substring(index + 1); + properties.setProperty(key.trim().toUpperCase(), value); } else { Log.w(TAG, "malformed contents: " + item); } diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java index 6d8cac0c..679250cb 100644 --- a/com/android/server/pm/BackgroundDexOptService.java +++ b/com/android/server/pm/BackgroundDexOptService.java @@ -340,7 +340,8 @@ public class BackgroundDexOptService extends JobService { int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | DexoptOptions.DEXOPT_BOOT_COMPLETE | - (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0); + (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) | + DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; if (is_for_primary_dex) { int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)); diff --git a/com/android/server/pm/Installer.java b/com/android/server/pm/Installer.java index 371b3ef5..210eb138 100644 --- a/com/android/server/pm/Installer.java +++ b/com/android/server/pm/Installer.java @@ -56,6 +56,8 @@ public class Installer extends SystemService { public static final int DEXOPT_STORAGE_CE = 1 << 7; /** Indicates that the dex file passed to dexopt in on DE storage. */ public static final int DEXOPT_STORAGE_DE = 1 << 8; + /** Indicates that dexopt is invoked from the background service. */ + public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9; // NOTE: keep in sync with installd public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8; diff --git a/com/android/server/pm/OtaDexoptService.java b/com/android/server/pm/OtaDexoptService.java index 6253857d..03f662a4 100644 --- a/com/android/server/pm/OtaDexoptService.java +++ b/com/android/server/pm/OtaDexoptService.java @@ -310,7 +310,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { collectingInstaller, mPackageManagerService.mInstallLock, mContext); String[] libraryDependencies = pkg.usesLibraryFiles; - if (pkg.isSystemApp()) { + if (pkg.isSystem()) { // For system apps, we want to avoid classpaths checks. libraryDependencies = NO_LIBRARIES; } diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java index cf0ffbb1..86a1c03d 100644 --- a/com/android/server/pm/PackageDexOptimizer.java +++ b/com/android/server/pm/PackageDexOptimizer.java @@ -54,6 +54,7 @@ import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX; import static com.android.server.pm.Installer.DEXOPT_FORCE; import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE; import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE; +import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; @@ -110,7 +111,7 @@ public class PackageDexOptimizer { } // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false. - if (pkg.isPrivilegedApp()) { + if (pkg.isPrivileged()) { return SystemProperties.getBoolean("pm.dexopt.priv-apps", true); } @@ -612,6 +613,9 @@ public class PackageDexOptimizer { if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) { flagsList.add("storage_de"); } + if ((flags & DEXOPT_IDLE_BACKGROUND_JOB) == DEXOPT_IDLE_BACKGROUND_JOB) { + flagsList.add("idle_background_job"); + } return String.join(",", flagsList); } diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java index 391deb74..7be0cde4 100644 --- a/com/android/server/pm/PackageManagerService.java +++ b/com/android/server/pm/PackageManagerService.java @@ -397,7 +397,7 @@ public class PackageManagerService extends IPackageManager.Stub static final boolean DEBUG_UPGRADE = false; static final boolean DEBUG_DOMAIN_VERIFICATION = false; private static final boolean DEBUG_BACKUP = false; - private static final boolean DEBUG_INSTALL = false; + public static final boolean DEBUG_INSTALL = false; public static final boolean DEBUG_REMOVE = false; private static final boolean DEBUG_BROADCASTS = false; private static final boolean DEBUG_SHOW_INFO = false; @@ -522,7 +522,7 @@ public class PackageManagerService extends IPackageManager.Stub */ private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW; - static final String PLATFORM_PACKAGE_NAME = "android"; + public static final String PLATFORM_PACKAGE_NAME = "android"; static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer"; @@ -542,18 +542,6 @@ public class PackageManagerService extends IPackageManager.Stub private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; - /** Permission grant: not grant the permission. */ - private static final int GRANT_DENIED = 1; - - /** Permission grant: grant the permission as an install permission. */ - private static final int GRANT_INSTALL = 2; - - /** Permission grant: grant the permission as a runtime one. */ - private static final int GRANT_RUNTIME = 3; - - /** Permission grant: grant as runtime a permission that was granted as an install time one. */ - private static final int GRANT_UPGRADE = 4; - /** Canonical intent used to identify what counts as a "web browser" app */ private static final Intent sBrowserIntent; static { @@ -753,9 +741,6 @@ public class PackageManagerService extends IPackageManager.Stub PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy; - // System configuration read by SystemConfig. - final int[] mGlobalGids; - final SparseArray<ArraySet<String>> mSystemPermissions; @GuardedBy("mAvailableFeatures") final ArrayMap<String, FeatureInfo> mAvailableFeatures; @@ -938,10 +923,6 @@ public class PackageManagerService extends IPackageManager.Stub final ArrayMap<ComponentName, PackageParser.Instrumentation> mInstrumentation = new ArrayMap<ComponentName, PackageParser.Instrumentation>(); - // Mapping from permission names to info about them. - final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups = - new ArrayMap<String, PackageParser.PermissionGroup>(); - // Packages whose data we have transfered into another package, thus // should no longer exist. final ArraySet<String> mTransferedPackages = new ArraySet<String>(); @@ -1016,8 +997,6 @@ public class PackageManagerService extends IPackageManager.Stub private File mCacheDir; - private ArraySet<String> mPrivappPermissionsViolations; - private Future<?> mPrepareAppDataFuture; private static class IFVerificationParams { @@ -1405,8 +1384,6 @@ public class PackageManagerService extends IPackageManager.Stub final @NonNull String mServicesSystemSharedLibraryPackageName; final @NonNull String mSharedSystemSharedLibraryPackageName; - final boolean mPermissionReviewRequired; - private final PackageUsage mPackageUsage = new PackageUsage(); private final CompilerStats mCompilerStats = new CompilerStats(); @@ -1928,9 +1905,11 @@ public class PackageManagerService extends IPackageManager.Stub } } @Override - public void onPermissionUpdated(int userId) { + public void onPermissionUpdated(int[] updatedUserIds, boolean sync) { synchronized (mPackages) { - mSettings.writeRuntimePermissionsForUserLPr(userId, false); + for (int userId : updatedUserIds) { + mSettings.writeRuntimePermissionsForUserLPr(userId, sync); + } } } @Override @@ -2363,9 +2342,6 @@ public class PackageManagerService extends IPackageManager.Stub mContext = context; - mPermissionReviewRequired = context.getResources().getBoolean( - R.bool.config_permissionReviewRequired); - mFactoryTest = factoryTest; mOnlyCore = onlyCore; mMetrics = new DisplayMetrics(); @@ -2434,8 +2410,6 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "get system config"); SystemConfig systemConfig = SystemConfig.getInstance(); - mGlobalGids = systemConfig.getGlobalGids(); - mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures(); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -2872,13 +2846,14 @@ public class PackageManagerService extends IPackageManager.Stub // cases get permissions that the user didn't initially explicitly // allow... it would be nice to have some better way to handle // this situation. - int updateFlags = UPDATE_PERMISSIONS_ALL; - if (ver.sdkVersion != mSdkVersion) { + final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion); + if (sdkUpdated) { Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to " + mSdkVersion + "; regranting permissions for internal storage"); - updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL; } - updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags); + mPermissionManager.updateAllPermissions( + StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(), + mPermissionCallback); ver.sdkVersion = mSdkVersion; // If this is the first boot or an update from pre-M, and it is a normal @@ -3597,7 +3572,7 @@ public class PackageManagerService extends IPackageManager.Stub for (String packageName : packages) { PackageParser.Package pkg = mPackages.get(packageName); if (pkg != null) { - if (!pkg.isSystemApp()) { + if (!pkg.isSystem()) { Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>"); continue; } @@ -3744,19 +3719,16 @@ public class PackageManagerService extends IPackageManager.Stub * <p> * Currently, there are three cases in which this can occur: * <ol> - * <li>The calling application is a "special" process. The special - * processes are {@link Process#SYSTEM_UID}, {@link Process#SHELL_UID} - * and {@code 0}</li> + * <li>The calling application is a "special" process. Special processes + * are those with a UID < {@link Process#FIRST_APPLICATION_UID}.</li> * <li>The calling application has the permission - * {@link android.Manifest.permission#ACCESS_INSTANT_APPS}</li> + * {@link android.Manifest.permission#ACCESS_INSTANT_APPS}.</li> * <li>The calling application is the default launcher on the * system partition.</li> * </ol> */ private boolean canViewInstantApps(int callingUid, int userId) { - if (callingUid == Process.SYSTEM_UID - || callingUid == Process.SHELL_UID - || callingUid == Process.ROOT_UID) { + if (callingUid < Process.FIRST_APPLICATION_UID) { return true; } if (mContext.checkCallingOrSelfPermission( @@ -4228,44 +4200,22 @@ public class PackageManagerService extends IPackageManager.Stub @Override public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName, int flags) { - // TODO Move this to PermissionManager when mPermissionGroups is moved there - synchronized (mPackages) { - if (groupName != null && !mPermissionGroups.containsKey(groupName)) { - // This is thrown as NameNotFoundException - return null; - } - } - return new ParceledListSlice<>( - mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid())); + final List<PermissionInfo> permissionList = + mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid()); + return (permissionList == null) ? null : new ParceledListSlice<>(permissionList); } @Override - public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return null; - } - // reader - synchronized (mPackages) { - return PackageParser.generatePermissionGroupInfo( - mPermissionGroups.get(name), flags); - } + public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) { + return mPermissionManager.getPermissionGroupInfo(groupName, flags, getCallingUid()); } @Override public @NonNull ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(int flags) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return ParceledListSlice.emptyList(); - } - // reader - synchronized (mPackages) { - final int N = mPermissionGroups.size(); - ArrayList<PermissionGroupInfo> out - = new ArrayList<PermissionGroupInfo>(N); - for (PackageParser.PermissionGroup pg : mPermissionGroups.values()) { - out.add(PackageParser.generatePermissionGroupInfo(pg, flags)); - } - return new ParceledListSlice<>(out); - } + final List<PermissionGroupInfo> permissionList = + mPermissionManager.getAllPermissionGroups(flags, getCallingUid()); + return (permissionList == null) + ? ParceledListSlice.emptyList() : new ParceledListSlice<>(permissionList); } private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags, @@ -5138,59 +5088,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public int checkUidPermission(String permName, int uid) { - final int callingUid = Binder.getCallingUid(); - final int callingUserId = UserHandle.getUserId(callingUid); - final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null; - final boolean isUidInstantApp = getInstantAppPackageName(uid) != null; - final int userId = UserHandle.getUserId(uid); - if (!sUserManager.exists(userId)) { - return PackageManager.PERMISSION_DENIED; - } - - synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); - if (obj != null) { - if (obj instanceof SharedUserSetting) { - if (isCallerInstantApp) { - return PackageManager.PERMISSION_DENIED; - } - } else if (obj instanceof PackageSetting) { - final PackageSetting ps = (PackageSetting) obj; - if (filterAppAccessLPr(ps, callingUid, callingUserId)) { - return PackageManager.PERMISSION_DENIED; - } - } - final SettingBase settingBase = (SettingBase) obj; - final PermissionsState permissionsState = settingBase.getPermissionsState(); - if (permissionsState.hasPermission(permName, userId)) { - if (isUidInstantApp) { - if (mSettings.mPermissions.isPermissionInstant(permName)) { - return PackageManager.PERMISSION_GRANTED; - } - } else { - return PackageManager.PERMISSION_GRANTED; - } - } - // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION - if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState - .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) { - return PackageManager.PERMISSION_GRANTED; - } - } else { - ArraySet<String> perms = mSystemPermissions.get(uid); - if (perms != null) { - if (perms.contains(permName)) { - return PackageManager.PERMISSION_GRANTED; - } - if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms - .contains(Manifest.permission.ACCESS_FINE_LOCATION)) { - return PackageManager.PERMISSION_GRANTED; - } - } - } - } - - return PackageManager.PERMISSION_DENIED; + return mPermissionManager.checkUidPermission(permName, uid, getCallingUid()); } @Override @@ -5352,7 +5250,9 @@ public class PackageManagerService extends IPackageManager.Stub } synchronized (mPackages) { - updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL); + mPermissionManager.updateAllPermissions( + StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(), + mPermissionCallback); for (int userId : UserManagerService.getInstance().getUserIds()) { final int packageCount = mPackages.size(); for (int i = 0; i < packageCount; i++) { @@ -5369,7 +5269,8 @@ public class PackageManagerService extends IPackageManager.Stub @Override public int getPermissionFlags(String permName, String packageName, int userId) { - return mPermissionManager.getPermissionFlags(permName, packageName, getCallingUid(), userId); + return mPermissionManager.getPermissionFlags( + permName, packageName, getCallingUid(), userId); } @Override @@ -9914,7 +9815,7 @@ public class PackageManagerService extends IPackageManager.Stub // it is better for the user to reinstall than to be in an limbo // state. Also libs disappearing under an app should never happen // - just in case. - if (!pkg.isSystemApp() || pkg.isUpdatedSystemApp()) { + if (!pkg.isSystem() || pkg.isUpdatedSystemApp()) { final int flags = pkg.isUpdatedSystemApp() ? PackageManager.DELETE_KEEP_DATA : 0; deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(), @@ -10063,7 +9964,7 @@ public class PackageManagerService extends IPackageManager.Stub assertPackageIsValid(pkg, policyFlags, scanFlags); if (Build.IS_DEBUGGABLE && - pkg.isPrivilegedApp() && + pkg.isPrivileged() && !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) { PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg); } @@ -11156,54 +11057,15 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Activities: " + r); } - N = pkg.permissionGroups.size(); - r = null; - for (i=0; i<N; i++) { - PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i); - PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name); - final String curPackageName = cur == null ? null : cur.info.packageName; - // Dont allow ephemeral apps to define new permission groups. - if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { - Slog.w(TAG, "Permission group " + pg.info.name + " from package " - + pg.info.packageName - + " ignored: instant apps cannot define new permission groups."); - continue; - } - final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName); - if (cur == null || isPackageUpdate) { - mPermissionGroups.put(pg.info.name, pg); - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - if (isPackageUpdate) { - r.append("UPD:"); - } - r.append(pg.info.name); - } - } else { - Slog.w(TAG, "Permission group " + pg.info.name + " from package " - + pg.info.packageName + " ignored: original from " - + cur.info.packageName); - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append("DUP:"); - r.append(pg.info.name); - } - } - } - if (r != null) { - if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permission Groups: " + r); + // Don't allow ephemeral applications to define new permissions groups. + if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { + Slog.w(TAG, "Permission groups from package " + pkg.packageName + + " ignored: instant apps cannot define new permission groups."); + } else { + mPermissionManager.addAllPermissionGroups(pkg, chatty); } - - // Dont allow ephemeral apps to define new permissions. + // Don't allow ephemeral applications to define new permissions. if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { Slog.w(TAG, "Permissions from package " + pkg.packageName + " ignored: instant apps cannot define new permissions."); @@ -11995,611 +11857,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - public static final int UPDATE_PERMISSIONS_ALL = 1<<0; - public static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1; - public static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2; - - private void updatePermissionsLPw(PackageParser.Package pkg, int flags) { - // Update the parent permissions - updatePermissionsLPw(pkg.packageName, pkg, flags); - // Update the child permissions - final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; - for (int i = 0; i < childCount; i++) { - PackageParser.Package childPkg = pkg.childPackages.get(i); - updatePermissionsLPw(childPkg.packageName, childPkg, flags); - } - } - - private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo, - int flags) { - final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null; - updatePermissionsLocked(changingPkg, pkgInfo, volumeUuid, flags); - } - - private void updatePermissionsLocked(String changingPkg, - PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) { - // TODO: Most of the methods exposing BasePermission internals [source package name, - // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't - // have package settings, we should make note of it elsewhere [map between - // source package name and BasePermission] and cycle through that here. Then we - // define a single method on BasePermission that takes a PackageSetting, changing - // package name and a package. - // NOTE: With this approach, we also don't need to tree trees differently than - // normal permissions. Today, we need two separate loops because these BasePermission - // objects are stored separately. - // Make sure there are no dangling permission trees. - flags = mPermissionManager.updatePermissionTrees(changingPkg, pkgInfo, flags); - - // Make sure all dynamic permissions have been assigned to a package, - // and make sure there are no dangling permissions. - flags = mPermissionManager.updatePermissions(changingPkg, pkgInfo, flags); - - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions"); - // Now update the permissions for all packages, in particular - // replace the granted permissions of the system packages. - if ((flags&UPDATE_PERMISSIONS_ALL) != 0) { - for (PackageParser.Package pkg : mPackages.values()) { - if (pkg != pkgInfo) { - // Only replace for packages on requested volume - final String volumeUuid = getVolumeUuidForPackage(pkg); - final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0) - && Objects.equals(replaceVolumeUuid, volumeUuid); - grantPermissionsLPw(pkg, replace, changingPkg); - } - } - } - - if (pkgInfo != null) { - // Only replace for packages on requested volume - final String volumeUuid = getVolumeUuidForPackage(pkgInfo); - final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0) - && Objects.equals(replaceVolumeUuid, volumeUuid); - grantPermissionsLPw(pkgInfo, replace, changingPkg); - } - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - - private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace, - String packageOfInterest) { - // IMPORTANT: There are two types of permissions: install and runtime. - // Install time permissions are granted when the app is installed to - // all device users and users added in the future. Runtime permissions - // are granted at runtime explicitly to specific users. Normal and signature - // protected permissions are install time permissions. Dangerous permissions - // are install permissions if the app's target SDK is Lollipop MR1 or older, - // otherwise they are runtime permissions. This function does not manage - // runtime permissions except for the case an app targeting Lollipop MR1 - // being upgraded to target a newer SDK, in which case dangerous permissions - // are transformed from install time to runtime ones. - - final PackageSetting ps = (PackageSetting) pkg.mExtras; - if (ps == null) { - return; - } - - PermissionsState permissionsState = ps.getPermissionsState(); - PermissionsState origPermissions = permissionsState; - - final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); - - boolean runtimePermissionsRevoked = false; - int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY; - - boolean changedInstallPermission = false; - - if (replace) { - ps.installPermissionsFixed = false; - if (!ps.isSharedUser()) { - origPermissions = new PermissionsState(permissionsState); - permissionsState.reset(); - } else { - // We need to know only about runtime permission changes since the - // calling code always writes the install permissions state but - // the runtime ones are written only if changed. The only cases of - // changed runtime permissions here are promotion of an install to - // runtime and revocation of a runtime from a shared user. - changedRuntimePermissionUserIds = - mPermissionManager.revokeUnusedSharedUserPermissions( - ps.sharedUser, UserManagerService.getInstance().getUserIds()); - if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) { - runtimePermissionsRevoked = true; - } - } - } - - permissionsState.setGlobalGids(mGlobalGids); - - final int N = pkg.requestedPermissions.size(); - for (int i=0; i<N; i++) { - final String name = pkg.requestedPermissions.get(i); - final BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(name); - final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion - >= Build.VERSION_CODES.M; - - if (DEBUG_INSTALL) { - Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); - } - - if (bp == null || bp.getSourcePackageSetting() == null) { - if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Unknown permission " + name - + " in package " + pkg.packageName); - } - } - continue; - } - - - // Limit ephemeral apps to ephemeral allowed permissions. - if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) { - if (DEBUG_PERMISSIONS) { - Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + " for package " - + pkg.packageName); - } - continue; - } - - if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { - if (DEBUG_PERMISSIONS) { - Log.i(TAG, "Denying runtime-only permission " + bp.getName() + " for package " - + pkg.packageName); - } - continue; - } - - final String perm = bp.getName(); - boolean allowedSig = false; - int grant = GRANT_DENIED; - - // Keep track of app op permissions. - if (bp.isAppOp()) { - mSettings.addAppOpPackage(perm, pkg.packageName); - } - - if (bp.isNormal()) { - // For all apps normal permissions are install time ones. - grant = GRANT_INSTALL; - } else if (bp.isRuntime()) { - // If a permission review is required for legacy apps we represent - // their permissions as always granted runtime ones since we need - // to keep the review required permission flag per user while an - // install permission's state is shared across all users. - if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) { - // For legacy apps dangerous permissions are install time ones. - grant = GRANT_INSTALL; - } else if (origPermissions.hasInstallPermission(bp.getName())) { - // For legacy apps that became modern, install becomes runtime. - grant = GRANT_UPGRADE; - } else if (mPromoteSystemApps - && isSystemApp(ps) - && mExistingSystemPackages.contains(ps.name)) { - // For legacy system apps, install becomes runtime. - // We cannot check hasInstallPermission() for system apps since those - // permissions were granted implicitly and not persisted pre-M. - grant = GRANT_UPGRADE; - } else { - // For modern apps keep runtime permissions unchanged. - grant = GRANT_RUNTIME; - } - } else if (bp.isSignature()) { - // For all apps signature permissions are install time ones. - allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions); - if (allowedSig) { - grant = GRANT_INSTALL; - } - } - - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName); - } - - if (grant != GRANT_DENIED) { - if (!isSystemApp(ps) && ps.installPermissionsFixed) { - // If this is an existing, non-system package, then - // we can't add any new permissions to it. - if (!allowedSig && !origPermissions.hasInstallPermission(perm)) { - // Except... if this is a permission that was added - // to the platform (note: need to only do this when - // updating the platform). - if (!isNewPlatformPermissionForPackage(perm, pkg)) { - grant = GRANT_DENIED; - } - } - } - - switch (grant) { - case GRANT_INSTALL: { - // Revoke this as runtime permission to handle the case of - // a runtime permission being downgraded to an install one. - // Also in permission review mode we keep dangerous permissions - // for legacy apps - for (int userId : UserManagerService.getInstance().getUserIds()) { - if (origPermissions.getRuntimePermissionState( - perm, userId) != null) { - // Revoke the runtime permission and clear the flags. - origPermissions.revokeRuntimePermission(bp, userId); - origPermissions.updatePermissionFlags(bp, userId, - PackageManager.MASK_PERMISSION_FLAGS, 0); - // If we revoked a permission permission, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - // Grant an install permission. - if (permissionsState.grantInstallPermission(bp) != - PermissionsState.PERMISSION_OPERATION_FAILURE) { - changedInstallPermission = true; - } - } break; - - case GRANT_RUNTIME: { - // Grant previously granted runtime permissions. - for (int userId : UserManagerService.getInstance().getUserIds()) { - PermissionState permissionState = origPermissions - .getRuntimePermissionState(perm, userId); - int flags = permissionState != null - ? permissionState.getFlags() : 0; - if (origPermissions.hasRuntimePermission(perm, userId)) { - // Don't propagate the permission in a permission review mode if - // the former was revoked, i.e. marked to not propagate on upgrade. - // Note that in a permission review mode install permissions are - // represented as constantly granted runtime ones since we need to - // keep a per user state associated with the permission. Also the - // revoke on upgrade flag is no longer applicable and is reset. - final boolean revokeOnUpgrade = (flags & PackageManager - .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0; - if (revokeOnUpgrade) { - flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; - // Since we changed the flags, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - if (!mPermissionReviewRequired || !revokeOnUpgrade) { - if (permissionsState.grantRuntimePermission(bp, userId) == - PermissionsState.PERMISSION_OPERATION_FAILURE) { - // If we cannot put the permission as it was, - // we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - - // If the app supports runtime permissions no need for a review. - if (mPermissionReviewRequired - && appSupportsRuntimePermissions - && (flags & PackageManager - .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; - // Since we changed the flags, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } else if (mPermissionReviewRequired - && !appSupportsRuntimePermissions) { - // For legacy apps that need a permission review, every new - // runtime permission is granted but it is pending a review. - // We also need to review only platform defined runtime - // permissions as these are the only ones the platform knows - // how to disable the API to simulate revocation as legacy - // apps don't expect to run with revoked permissions. - if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) { - if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { - flags |= FLAG_PERMISSION_REVIEW_REQUIRED; - // We changed the flags, hence have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - if (permissionsState.grantRuntimePermission(bp, userId) - != PermissionsState.PERMISSION_OPERATION_FAILURE) { - // We changed the permission, hence have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - // Propagate the permission flags. - permissionsState.updatePermissionFlags(bp, userId, flags, flags); - } - } break; - - case GRANT_UPGRADE: { - // Grant runtime permissions for a previously held install permission. - PermissionState permissionState = origPermissions - .getInstallPermissionState(perm); - final int flags = permissionState != null ? permissionState.getFlags() : 0; - - if (origPermissions.revokeInstallPermission(bp) - != PermissionsState.PERMISSION_OPERATION_FAILURE) { - // We will be transferring the permission flags, so clear them. - origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL, - PackageManager.MASK_PERMISSION_FLAGS, 0); - changedInstallPermission = true; - } - - // If the permission is not to be promoted to runtime we ignore it and - // also its other flags as they are not applicable to install permissions. - if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) { - for (int userId : currentUserIds) { - if (permissionsState.grantRuntimePermission(bp, userId) != - PermissionsState.PERMISSION_OPERATION_FAILURE) { - // Transfer the permission flags. - permissionsState.updatePermissionFlags(bp, userId, - flags, flags); - // If we granted the permission, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); - } - } - } - } break; - - default: { - if (packageOfInterest == null - || packageOfInterest.equals(pkg.packageName)) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " because it was previously installed without"); - } - } - } break; - } - } else { - if (permissionsState.revokeInstallPermission(bp) != - PermissionsState.PERMISSION_OPERATION_FAILURE) { - // Also drop the permission flags. - permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, - PackageManager.MASK_PERMISSION_FLAGS, 0); - changedInstallPermission = true; - Slog.i(TAG, "Un-granting permission " + perm - + " from package " + pkg.packageName - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) - + ")"); - } else if (bp.isAppOp()) { - // Don't print warning for app op permissions, since it is fine for them - // not to be granted, there is a UI for the user to decide. - if (DEBUG_PERMISSIONS - && (packageOfInterest == null - || packageOfInterest.equals(pkg.packageName))) { - Slog.i(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) - + ")"); - } - } - } - } - - if ((changedInstallPermission || replace) && !ps.installPermissionsFixed && - !isSystemApp(ps) || isUpdatedSystemApp(ps)){ - // This is the first that we have heard about this package, so the - // permissions we have now selected are fixed until explicitly - // changed. - ps.installPermissionsFixed = true; - } - - // Persist the runtime permissions state for users with changes. If permissions - // were revoked because no app in the shared user declares them we have to - // write synchronously to avoid losing runtime permissions state. - for (int userId : changedRuntimePermissionUserIds) { - mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked); - } - } - - private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { - boolean allowed = false; - final int NP = PackageParser.NEW_PERMISSIONS.length; - for (int ip=0; ip<NP; ip++) { - final PackageParser.NewPermissionInfo npi - = PackageParser.NEW_PERMISSIONS[ip]; - if (npi.name.equals(perm) - && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) { - allowed = true; - Log.i(TAG, "Auto-granting " + perm + " to old pkg " - + pkg.packageName); - break; - } - } - return allowed; - } - - /** - * Determines whether a package is whitelisted for a particular privapp permission. - * - * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted. - * - * <p>This handles parent/child apps. - */ - private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) { - ArraySet<String> wlPermissions = SystemConfig.getInstance() - .getPrivAppPermissions(pkg.packageName); - // Let's check if this package is whitelisted... - boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm); - // If it's not, we'll also tail-recurse to the parent. - return whitelisted || - pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage); - } - - private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, - BasePermission bp, PermissionsState origPermissions) { - boolean oemPermission = bp.isOEM(); - boolean privilegedPermission = bp.isPrivileged(); - boolean privappPermissionsDisable = - RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE; - boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName()); - boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName); - if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp() - && !platformPackage && platformPermission) { - if (!hasPrivappWhitelistEntry(perm, pkg)) { - Slog.w(TAG, "Privileged permission " + perm + " for package " - + pkg.packageName + " - not in privapp-permissions whitelist"); - // Only report violations for apps on system image - if (!mSystemReady && !pkg.isUpdatedSystemApp()) { - // it's only a reportable violation if the permission isn't explicitly denied - final ArraySet<String> deniedPermissions = SystemConfig.getInstance() - .getPrivAppDenyPermissions(pkg.packageName); - final boolean permissionViolation = - deniedPermissions == null || !deniedPermissions.contains(perm); - if (permissionViolation - && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - if (mPrivappPermissionsViolations == null) { - mPrivappPermissionsViolations = new ArraySet<>(); - } - mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm); - } else { - return false; - } - } - if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - return false; - } - } - } - boolean allowed = (compareSignatures( - bp.getSourcePackageSetting().signatures.mSignatures, pkg.mSignatures) - == PackageManager.SIGNATURE_MATCH) - || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) - == PackageManager.SIGNATURE_MATCH); - if (!allowed && (privilegedPermission || oemPermission)) { - if (isSystemApp(pkg)) { - // For updated system applications, a privileged/oem permission - // is granted only if it had been defined by the original application. - if (pkg.isUpdatedSystemApp()) { - final PackageSetting sysPs = mSettings - .getDisabledSystemPkgLPr(pkg.packageName); - if (sysPs != null && sysPs.getPermissionsState().hasInstallPermission(perm)) { - // If the original was granted this permission, we take - // that grant decision as read and propagate it to the - // update. - if ((privilegedPermission && sysPs.isPrivileged()) - || (oemPermission && sysPs.isOem() - && canGrantOemPermission(sysPs, perm))) { - allowed = true; - } - } else { - // The system apk may have been updated with an older - // version of the one on the data partition, but which - // granted a new system permission that it didn't have - // before. In this case we do want to allow the app to - // now get the new permission if the ancestral apk is - // privileged to get it. - if (sysPs != null && sysPs.pkg != null - && isPackageRequestingPermission(sysPs.pkg, perm) - && ((privilegedPermission && sysPs.isPrivileged()) - || (oemPermission && sysPs.isOem() - && canGrantOemPermission(sysPs, perm)))) { - allowed = true; - } - // Also if a privileged parent package on the system image or any of - // its children requested a privileged/oem permission, the updated child - // packages can also get the permission. - if (pkg.parentPackage != null) { - final PackageSetting disabledSysParentPs = mSettings - .getDisabledSystemPkgLPr(pkg.parentPackage.packageName); - final PackageParser.Package disabledSysParentPkg = - (disabledSysParentPs == null || disabledSysParentPs.pkg == null) - ? null : disabledSysParentPs.pkg; - if (disabledSysParentPkg != null - && ((privilegedPermission && disabledSysParentPs.isPrivileged()) - || (oemPermission && disabledSysParentPs.isOem()))) { - if (isPackageRequestingPermission(disabledSysParentPkg, perm) - && canGrantOemPermission(disabledSysParentPs, perm)) { - allowed = true; - } else if (disabledSysParentPkg.childPackages != null) { - final int count = disabledSysParentPkg.childPackages.size(); - for (int i = 0; i < count; i++) { - final PackageParser.Package disabledSysChildPkg = - disabledSysParentPkg.childPackages.get(i); - final PackageSetting disabledSysChildPs = - mSettings.getDisabledSystemPkgLPr( - disabledSysChildPkg.packageName); - if (isPackageRequestingPermission(disabledSysChildPkg, perm) - && canGrantOemPermission( - disabledSysChildPs, perm)) { - allowed = true; - break; - } - } - } - } - } - } - } else { - allowed = (privilegedPermission && isPrivilegedApp(pkg)) - || (oemPermission && isOemApp(pkg) - && canGrantOemPermission( - mSettings.getPackageLPr(pkg.packageName), perm)); - } - } - } - if (!allowed) { - if (!allowed - && bp.isPre23() - && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { - // If this was a previously normal/dangerous permission that got moved - // to a system permission as part of the runtime permission redesign, then - // we still want to blindly grant it to old apps. - allowed = true; - } - if (!allowed && bp.isInstaller() - && pkg.packageName.equals(mRequiredInstallerPackage)) { - // If this permission is to be granted to the system installer and - // this app is an installer, then it gets the permission. - allowed = true; - } - if (!allowed && bp.isVerifier() - && pkg.packageName.equals(mRequiredVerifierPackage)) { - // If this permission is to be granted to the system verifier and - // this app is a verifier, then it gets the permission. - allowed = true; - } - if (!allowed && bp.isPreInstalled() - && isSystemApp(pkg)) { - // Any pre-installed system app is allowed to get this permission. - allowed = true; - } - if (!allowed && bp.isDevelopment()) { - // For development permissions, a development permission - // is granted only if it was already granted. - allowed = origPermissions.hasInstallPermission(perm); - } - if (!allowed && bp.isSetup() - && pkg.packageName.equals(mSetupWizardPackage)) { - // If this permission is to be granted to the system setup wizard and - // this app is a setup wizard, then it gets the permission. - allowed = true; - } - } - return allowed; - } - - private static boolean canGrantOemPermission(PackageSetting ps, String permission) { - if (!ps.isOem()) { - return false; - } - // all oem permissions must explicitly be granted or denied - final Boolean granted = - SystemConfig.getInstance().getOemPermissions(ps.name).get(permission); - if (granted == null) { - throw new IllegalStateException("OEM permission" + permission + " requested by package " - + ps.name + " must be explicitly declared granted or not"); - } - return Boolean.TRUE == granted; - } - - private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) { - final int permCount = pkg.requestedPermissions.size(); - for (int j = 0; j < permCount; j++) { - String requestedPermission = pkg.requestedPermissions.get(j); - if (permission.equals(requestedPermission)) { - return true; - } - } - return false; - } final class ActivityIntentResolver extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> { @@ -16491,7 +15748,7 @@ public class PackageManagerService extends IPackageManager.Stub } // don't allow a system upgrade unless the upgrade hash matches - if (oldPackage.restrictUpdateHash != null && oldPackage.isSystemApp()) { + if (oldPackage.restrictUpdateHash != null && oldPackage.isSystem()) { byte[] digestBytes = null; try { final MessageDigest digest = MessageDigest.getInstance("SHA-512"); @@ -16758,7 +16015,9 @@ public class PackageManagerService extends IPackageManager.Stub setInstallerPackageNameLPw(deletedPackage, installerPackageName); // Update permissions for restored package - updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL); + mPermissionManager.updatePermissions( + deletedPackage.packageName, deletedPackage, false, mPackages.values(), + mPermissionCallback); mSettings.writeLPr(); } @@ -16900,7 +16159,9 @@ public class PackageManagerService extends IPackageManager.Stub setInstallerPackageNameLPw(deletedPackage, installerPackageName); // Update permissions for restored package - updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL); + mPermissionManager.updatePermissions( + deletedPackage.packageName, deletedPackage, false, mPackages.values(), + mPermissionCallback); mSettings.writeLPr(); } @@ -17013,12 +16274,12 @@ public class PackageManagerService extends IPackageManager.Stub } } - private void updateSettingsInternalLI(PackageParser.Package newPackage, + private void updateSettingsInternalLI(PackageParser.Package pkg, String installerPackageName, int[] allUsers, int[] installedForUsers, PackageInstalledInfo res, UserHandle user, int installReason) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); - String pkgName = newPackage.packageName; + String pkgName = pkg.packageName; synchronized (mPackages) { //write settings. the installStatus will be incomplete at this stage. //note that the new package setting would have already been @@ -17030,18 +16291,18 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.codePath); + if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath); synchronized (mPackages) { - updatePermissionsLPw(newPackage.packageName, newPackage, - UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0 - ? UPDATE_PERMISSIONS_ALL : 0)); +// NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions + mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(), + mPermissionCallback); // For system-bundled packages, we assume that installing an upgraded version // of the package implies that the user actually wants to run that new code, // so we enable the package. PackageSetting ps = mSettings.mPackages.get(pkgName); final int userId = user.getIdentifier(); if (ps != null) { - if (isSystemApp(newPackage)) { + if (isSystemApp(pkg)) { if (DEBUG_INSTALL) { Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName); } @@ -17101,8 +16362,8 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.writeKernelMappingLPr(ps); } res.name = pkgName; - res.uid = newPackage.applicationInfo.uid; - res.pkg = newPackage; + res.uid = pkg.applicationInfo.uid; + res.pkg = pkg; mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE); mSettings.setInstallerPackageName(pkgName, installerPackageName); res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); @@ -17801,18 +17062,6 @@ public class PackageManagerService extends IPackageManager.Stub return installFlags; } - private String getVolumeUuidForPackage(PackageParser.Package pkg) { - if (isExternal(pkg)) { - if (TextUtils.isEmpty(pkg.volumeUuid)) { - return StorageManager.UUID_PRIMARY_PHYSICAL; - } else { - return pkg.volumeUuid; - } - } else { - return StorageManager.UUID_PRIVATE_INTERNAL; - } - } - private VersionInfo getSettingsVersionForPackage(PackageParser.Package pkg) { if (isExternal(pkg)) { if (TextUtils.isEmpty(pkg.volumeUuid)) { @@ -18457,7 +17706,8 @@ public class PackageManagerService extends IPackageManager.Stub if (outInfo != null) { outInfo.removedAppId = removedAppId; } - updatePermissionsLPw(deletedPs.name, null, 0); + mPermissionManager.updatePermissions( + deletedPs.name, null, false, mPackages.values(), mPermissionCallback); if (deletedPs.sharedUser != null) { // Remove permissions associated with package. Since runtime // permissions are per user we have to kill the removed package @@ -18660,21 +17910,21 @@ public class PackageManagerService extends IPackageManager.Stub parseFlags |= PackageParser.PARSE_IS_OEM; } - final PackageParser.Package newPkg = + final PackageParser.Package pkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null); try { // update shared libraries for the newly re-installed system package - updateSharedLibrariesLPr(newPkg, null); + updateSharedLibrariesLPr(pkg, null); } catch (PackageManagerException e) { Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); } - prepareAppDataAfterInstallLIF(newPkg); + prepareAppDataAfterInstallLIF(pkg); // writer synchronized (mPackages) { - PackageSetting ps = mSettings.mPackages.get(newPkg.packageName); + PackageSetting ps = mSettings.mPackages.get(pkg.packageName); // Propagate the permissions state as we do not want to drop on the floor // runtime permissions. The update permissions method below will take @@ -18682,8 +17932,8 @@ public class PackageManagerService extends IPackageManager.Stub if (origPermissionState != null) { ps.getPermissionsState().copyFrom(origPermissionState); } - updatePermissionsLPw(newPkg.packageName, newPkg, - UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG); + mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(), + mPermissionCallback); final boolean applyUserRestrictions = (allUserHandles != null) && (origUserHandles != null); @@ -18716,7 +17966,7 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.writeLPr(); } } - return newPkg; + return pkg; } private boolean deleteInstalledPackageLIF(PackageSetting ps, @@ -19333,7 +18583,7 @@ public class PackageManagerService extends IPackageManager.Stub // If permission review is enabled and this is a legacy app, mark the // permission as requiring a review as this is the initial state. int flags = 0; - if (mPermissionReviewRequired + if (mSettings.mPermissions.mPermissionReviewRequired && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { flags |= FLAG_PERMISSION_REVIEW_REQUIRED; } @@ -20692,7 +19942,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // data partition and then replace the version on the system partition. final PackageParser.Package deletedPkg = pkgSetting.pkg; final boolean isSystemStub = deletedPkg.isStub - && deletedPkg.isSystemApp(); + && deletedPkg.isSystem(); if (isSystemStub && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) { @@ -20732,22 +19982,23 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); synchronized (mPackages) { disableSystemPackageLPw(deletedPkg, tmpPkg); } - final PackageParser.Package newPkg; + final PackageParser.Package pkg; try (PackageFreezer freezer = freezePackage(deletedPkg.packageName, "setEnabledSetting")) { final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | PackageParser.PARSE_ENFORCE_CODE; - newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, + pkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null /*user*/); - prepareAppDataAfterInstallLIF(newPkg); + prepareAppDataAfterInstallLIF(pkg); synchronized (mPackages) { try { - updateSharedLibrariesLPr(newPkg, null); + updateSharedLibrariesLPr(pkg, null); } catch (PackageManagerException e) { Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e); } - updatePermissionsLPw(newPkg.packageName, newPkg, - UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG); + mPermissionManager.updatePermissions( + pkg.packageName, pkg, true, mPackages.values(), + mPermissionCallback); mSettings.writeLPr(); } } catch (PackageManagerException e) { @@ -20787,11 +20038,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } return; } - clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE + clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); - clearAppProfilesLIF(newPkg, UserHandle.USER_ALL); - mDexManager.notifyPackageUpdated(newPkg.packageName, - newPkg.baseCodePath, newPkg.splitCodePaths); + clearAppProfilesLIF(pkg, UserHandle.USER_ALL); + mDexManager.notifyPackageUpdated(pkg.packageName, + pkg.baseCodePath, pkg.splitCodePaths); } } if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT @@ -21102,8 +20353,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // permissions, ensure permissions are updated. Beware of dragons if you // try optimizing this. synchronized (mPackages) { - updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, - UPDATE_PERMISSIONS_ALL); + mPermissionManager.updateAllPermissions( + StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(), + mPermissionCallback); } // Kick off any messages waiting for system ready @@ -21152,10 +20404,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL); reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL); - if (mPrivappPermissionsViolations != null) { - throw new IllegalStateException("Signature|privileged permissions not in " - + "privapp-permissions whitelist: " + mPrivappPermissionsViolations); - } + mPermissionManager.systemReady(); } public void waitForAppDataPrepared() { @@ -22140,13 +21389,13 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } synchronized (mPackages) { - int updateFlags = UPDATE_PERMISSIONS_ALL; - if (ver.sdkVersion != mSdkVersion) { + final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion); + if (sdkUpdated) { logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to " + mSdkVersion + "; regranting permissions for " + volumeUuid); - updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL; } - updatePermissionsLocked(null, null, volumeUuid, updateFlags); + mPermissionManager.updateAllPermissions(volumeUuid, sdkUpdated, mPackages.values(), + mPermissionCallback); // Yay, everything is now upgraded ver.forceCurrent(); @@ -22594,7 +21843,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); * requested by the app. */ private boolean maybeMigrateAppDataLIF(PackageParser.Package pkg, int userId) { - if (pkg.isSystemApp() && !StorageManager.isFileEncryptedNativeOrEmulated() + if (pkg.isSystem() && !StorageManager.isFileEncryptedNativeOrEmulated() && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) { final int storageTarget = pkg.applicationInfo.isDefaultToDeviceProtectedStorage() ? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE; @@ -23159,9 +22408,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // permissions to keep per user flag state whether review is needed. // Hence, if a new user is added we have to propagate dangerous // permission grants for these legacy apps. - if (mPermissionReviewRequired) { - updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL - | UPDATE_PERMISSIONS_REPLACE_ALL); + if (mSettings.mPermissions.mPermissionReviewRequired) { +// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG + mPermissionManager.updateAllPermissions( + StorageManager.UUID_PRIVATE_INTERNAL, true, mPackages.values(), + mPermissionCallback); } } } @@ -23606,13 +22857,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override - public PackageParser.PermissionGroup getPermissionGroupTEMP(String groupName) { - synchronized (mPackages) { - return mPermissionGroups.get(groupName); - } - } - - @Override public boolean isInstantApp(String packageName, int userId) { return PackageManagerService.this.isInstantApp(packageName, userId); } @@ -23750,24 +22994,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); @Override public boolean isPermissionsReviewRequired(String packageName, int userId) { synchronized (mPackages) { - // If we do not support permission review, done. - if (!mPermissionReviewRequired) { - return false; - } - - PackageSetting packageSetting = mSettings.mPackages.get(packageName); - if (packageSetting == null) { - return false; - } - - // Permission review applies only to apps not supporting the new permission model. - if (packageSetting.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) { - return false; - } - - // Legacy apps have the permission and get user consent on launch. - PermissionsState permissionsState = packageSetting.getPermissionsState(); - return permissionsState.isPermissionReviewRequired(userId); + return mPermissionManager.isPermissionsReviewRequired( + mPackages.get(packageName), userId); } } @@ -23921,6 +23149,16 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override + public boolean isLegacySystemApp(Package pkg) { + synchronized (mPackages) { + final PackageSetting ps = (PackageSetting) pkg.mExtras; + return mPromoteSystemApps + && ps.isSystem() + && mExistingSystemPackages.contains(ps.name); + } + } + + @Override public List<PackageInfo> getOverlayPackages(int userId) { final ArrayList<PackageInfo> overlayPackages = new ArrayList<PackageInfo>(); synchronized (mPackages) { diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java index 1fea003a..a2099e60 100644 --- a/com/android/server/pm/PackageManagerShellCommand.java +++ b/com/android/server/pm/PackageManagerShellCommand.java @@ -16,14 +16,17 @@ package com.android.server.pm; +import android.accounts.IAccountManager; import android.app.ActivityManager; 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.FeatureInfo; +import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; @@ -41,6 +44,7 @@ import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; import android.content.res.AssetManager; import android.content.res.Resources; @@ -49,15 +53,23 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.IUserManager; +import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.ShellCommand; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; +import android.os.storage.StorageManager; import android.text.TextUtils; -import android.util.ArrayMap; +import android.text.format.DateUtils; import android.util.ArraySet; import android.util.PrintWriterPrinter; + import com.android.internal.content.PackageHelper; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.SizedInputStream; import com.android.server.SystemConfig; @@ -81,6 +93,12 @@ import java.util.WeakHashMap; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; +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; + class PackageManagerShellCommand extends ShellCommand { /** Path for streaming APK content */ private static final String STDIN_PATH = "-"; @@ -107,6 +125,20 @@ class PackageManagerShellCommand extends ShellCommand { final PrintWriter pw = getOutPrintWriter(); try { switch(cmd) { + case "path": + return runPath(); + case "dump": + return runDump(); + case "list": + return runList(); + case "resolve-activity": + return runResolveActivity(); + case "query-activities": + return runQueryIntentActivities(); + case "query-services": + return runQueryIntentServices(); + case "query-receivers": + return runQueryIntentReceivers(); case "install": return runInstall(); case "install-abandon": @@ -122,44 +154,99 @@ class PackageManagerShellCommand extends ShellCommand { return runInstallWrite(); case "install-existing": return runInstallExisting(); + case "set-install-location": + return runSetInstallLocation(); + case "get-install-location": + return runGetInstallLocation(); + case "move-package": + return runMovePackage(); + case "move-primary-storage": + return runMovePrimaryStorage(); case "compile": return runCompile(); case "reconcile-secondary-dex-files": return runreconcileSecondaryDexFiles(); + case "force-dex-opt": + return runForceDexOpt(); case "bg-dexopt-job": return runDexoptJob(); case "dump-profiles": return runDumpProfiles(); - case "list": - return runList(); case "uninstall": return runUninstall(); - case "resolve-activity": - return runResolveActivity(); - case "query-activities": - return runQueryIntentActivities(); - case "query-services": - return runQueryIntentServices(); - case "query-receivers": - return runQueryIntentReceivers(); + case "clear": + return runClear(); + case "enable": + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + case "disable": + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED); + case "disable-user": + return runSetEnabledSetting( + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER); + case "disable-until-used": + return runSetEnabledSetting( + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); + case "default-state": + return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT); + case "hide": + return runSetHiddenSetting(true); + case "unhide": + return runSetHiddenSetting(false); case "suspend": return runSuspend(true); case "unsuspend": return runSuspend(false); - case "set-home-activity": - return runSetHomeActivity(); + case "grant": + return runGrantRevokePermission(true); + case "revoke": + return runGrantRevokePermission(false); + case "reset-permissions": + return runResetPermissions(); + case "set-permission-enforced": + return runSetPermissionEnforced(); case "get-privapp-permissions": return runGetPrivappPermissions(); case "get-privapp-deny-permissions": return runGetPrivappDenyPermissions(); case "get-oem-permissions": return runGetOemPermissions(); + case "set-app-link": + return runSetAppLink(); + case "get-app-link": + return runGetAppLink(); + case "trim-caches": + return runTrimCaches(); + case "create-user": + return runCreateUser(); + case "remove-user": + return runRemoveUser(); + case "set-user-restriction": + return runSetUserRestriction(); + case "get-max-users": + return runGetMaxUsers(); + case "set-home-activity": + return runSetHomeActivity(); + case "set-installer": + return runSetInstaller(); case "get-instantapp-resolver": return runGetInstantAppResolver(); case "has-feature": return runHasFeature(); - default: + default: { + String nextArg = getNextArg(); + if (nextArg == null) { + if (cmd.equalsIgnoreCase("-l")) { + return runListPackages(false); + } else if (cmd.equalsIgnoreCase("-lf")) { + return runListPackages(true); + } + } else if (getNextArg() == null) { + if (cmd.equalsIgnoreCase("-p")) { + return displayPackageFilePath(nextArg, UserHandle.USER_SYSTEM); + } + } return handleDefaultCommands(cmd); + } } } catch (RemoteException e) { pw.println("Remote exception: " + e); @@ -195,346 +282,40 @@ class PackageManagerShellCommand extends ShellCommand { } } } - - private int runInstall() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - final InstallParams params = makeInstallParams(); - final String inPath = getNextArg(); - - setParamsSize(params, inPath); - final int sessionId = doCreateSession(params.sessionParams, - params.installerPackageName, params.userId); - boolean abandonSession = true; - try { - if (inPath == null && params.sessionParams.sizeBytes == -1) { - pw.println("Error: must either specify a package size or an APK file"); - return 1; - } - if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk", - false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - if (doCommitSession(sessionId, false /*logSuccess*/) - != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - abandonSession = false; - pw.println("Success"); - return 0; - } finally { - if (abandonSession) { - try { - doAbandonSession(sessionId, false /*logSuccess*/); - } catch (Exception ignore) { + /** + * Displays the package file for a package. + * @param pckg + */ + private int displayPackageFilePath(String pckg, int userId) throws RemoteException { + PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId); + if (info != null && info.applicationInfo != null) { + final PrintWriter pw = getOutPrintWriter(); + pw.print("package:"); + pw.println(info.applicationInfo.sourceDir); + if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) { + for (String splitSourceDir : info.applicationInfo.splitSourceDirs) { + pw.print("package:"); + pw.println(splitSourceDir); } } - } - } - - private int runSuspend(boolean suspendedState) { - final PrintWriter pw = getOutPrintWriter(); - int userId = UserHandle.USER_SYSTEM; - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "--user": - userId = UserHandle.parseUserArg(getNextArgRequired()); - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } - } - - String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; - } - - try { - mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState, - userId); - pw.println("Package " + packageName + " new suspended state: " - + mInterface.isPackageSuspendedForUser(packageName, userId)); return 0; - } catch (RemoteException | IllegalArgumentException e) { - pw.println(e.toString()); - return 1; } + return 1; } - private int runInstallAbandon() throws RemoteException { - final int sessionId = Integer.parseInt(getNextArg()); - return doAbandonSession(sessionId, true /*logSuccess*/); - } - - private int runInstallCommit() throws RemoteException { - final int sessionId = Integer.parseInt(getNextArg()); - return doCommitSession(sessionId, true /*logSuccess*/); - } - - private int runInstallCreate() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - final InstallParams installParams = makeInstallParams(); - final int sessionId = doCreateSession(installParams.sessionParams, - installParams.installerPackageName, installParams.userId); - - // NOTE: adb depends on parsing this string - pw.println("Success: created install session [" + sessionId + "]"); - return 0; - } - - private int runInstallWrite() throws RemoteException { - long sizeBytes = -1; - - String opt; - while ((opt = getNextOption()) != null) { - if (opt.equals("-S")) { - sizeBytes = Long.parseLong(getNextArg()); - } else { - throw new IllegalArgumentException("Unknown option: " + opt); - } - } - - final int sessionId = Integer.parseInt(getNextArg()); - final String splitName = getNextArg(); - final String path = getNextArg(); - return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/); - } - - private int runInstallRemove() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - - final int sessionId = Integer.parseInt(getNextArg()); - - final String splitName = getNextArg(); - if (splitName == null) { - pw.println("Error: split name not specified"); - return 1; - } - return doRemoveSplit(sessionId, splitName, true /*logSuccess*/); - } - - private int runInstallExisting() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); + private int runPath() throws RemoteException { int userId = UserHandle.USER_SYSTEM; - int installFlags = 0; - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "--user": - userId = UserHandle.parseUserArg(getNextArgRequired()); - break; - case "--ephemeral": - case "--instant": - installFlags |= PackageManager.INSTALL_INSTANT_APP; - installFlags &= ~PackageManager.INSTALL_FULL_APP; - break; - case "--full": - installFlags &= ~PackageManager.INSTALL_INSTANT_APP; - installFlags |= PackageManager.INSTALL_FULL_APP; - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); } - final String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; - } - - try { - final int res = mInterface.installExistingPackageAsUser(packageName, userId, - installFlags, PackageManager.INSTALL_REASON_UNKNOWN); - if (res == PackageManager.INSTALL_FAILED_INVALID_URI) { - throw new NameNotFoundException("Package " + packageName + " doesn't exist"); - } - pw.println("Package " + packageName + " installed for user: " + userId); - return 0; - } catch (RemoteException | NameNotFoundException e) { - pw.println(e.toString()); - return 1; - } - } - - private int runCompile() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false); - boolean forceCompilation = false; - boolean allPackages = false; - boolean clearProfileData = false; - String compilerFilter = null; - String compilationReason = null; - String checkProfilesRaw = null; - boolean secondaryDex = false; - String split = null; - - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "-a": - allPackages = true; - break; - case "-c": - clearProfileData = true; - break; - case "-f": - forceCompilation = true; - break; - case "-m": - compilerFilter = getNextArgRequired(); - break; - case "-r": - compilationReason = getNextArgRequired(); - break; - case "--check-prof": - checkProfilesRaw = getNextArgRequired(); - break; - case "--reset": - forceCompilation = true; - clearProfileData = true; - compilationReason = "install"; - break; - case "--secondary-dex": - secondaryDex = true; - break; - case "--split": - split = getNextArgRequired(); - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } - } - - if (checkProfilesRaw != null) { - if ("true".equals(checkProfilesRaw)) { - checkProfiles = true; - } else if ("false".equals(checkProfilesRaw)) { - checkProfiles = false; - } else { - pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\"."); - return 1; - } - } - - if (compilerFilter != null && compilationReason != null) { - pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " + - "at the same time"); - return 1; - } - if (compilerFilter == null && compilationReason == null) { - pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " + - "reason (\"-r\") at the same time"); - return 1; - } - - if (allPackages && split != null) { - pw.println("-a cannot be specified together with --split"); - return 1; - } - - if (secondaryDex && split != null) { - pw.println("--secondary-dex cannot be specified together with --split"); - return 1; - } - - String targetCompilerFilter; - if (compilerFilter != null) { - if (!DexFile.isValidCompilerFilter(compilerFilter)) { - pw.println("Error: \"" + compilerFilter + - "\" is not a valid compilation filter."); - return 1; - } - targetCompilerFilter = compilerFilter; - } else { - int reason = -1; - for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { - if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals( - compilationReason)) { - reason = i; - break; - } - } - if (reason == -1) { - pw.println("Error: Unknown compilation reason: " + compilationReason); - return 1; - } - targetCompilerFilter = - PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason); - } - - - List<String> packageNames = null; - if (allPackages) { - packageNames = mInterface.getAllPackages(); - } else { - String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; - } - packageNames = Collections.singletonList(packageName); - } - - List<String> failedPackages = new ArrayList<>(); - for (String packageName : packageNames) { - if (clearProfileData) { - mInterface.clearApplicationProfileData(packageName); - } - - boolean result = secondaryDex - ? mInterface.performDexOptSecondary(packageName, - targetCompilerFilter, forceCompilation) - : mInterface.performDexOptMode(packageName, - checkProfiles, targetCompilerFilter, forceCompilation, - true /* bootComplete */, split); - if (!result) { - failedPackages.add(packageName); - } - } - - if (failedPackages.isEmpty()) { - pw.println("Success"); - return 0; - } else if (failedPackages.size() == 1) { - pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled"); - return 1; - } else { - pw.print("Failure: the following packages could not be compiled: "); - boolean is_first = true; - for (String packageName : failedPackages) { - if (is_first) { - is_first = false; - } else { - pw.print(", "); - } - pw.print(packageName); - } - pw.println(); + String pkg = getNextArgRequired(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); return 1; } - } - - private int runreconcileSecondaryDexFiles() throws RemoteException { - String packageName = getNextArg(); - mInterface.reconcileSecondaryDexFiles(packageName); - return 0; - } - - private int runDexoptJob() throws RemoteException { - boolean result = mInterface.runBackgroundDexoptJob(); - return result ? 0 : -1; - } - - private int runDumpProfiles() throws RemoteException { - String packageName = getNextArg(); - mInterface.dumpProfiles(packageName); - return 0; + return displayPackageFilePath(pkg, userId); } private int runList() throws RemoteException { @@ -558,6 +339,11 @@ class PackageManagerShellCommand extends ShellCommand { return runListPermissionGroups(); case "permissions": return runListPermissions(); + case "users": + ServiceManager.getService("user").shellCommand( + getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(), + new String[] { "list" }, getShellCallback(), adoptResultReceiver()); + return 0; } pw.println("Error: unknown list type '" + type + "'"); return -1; @@ -590,7 +376,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(); } else { pw.println("reqGlEsVersion=0x" - + Integer.toHexString(fi.reqGlEsVersion)); + + Integer.toHexString(fi.reqGlEsVersion)); } } return 0; @@ -872,111 +658,6 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } - private int runUninstall() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - int flags = 0; - int userId = UserHandle.USER_ALL; - int versionCode = PackageManager.VERSION_CODE_HIGHEST; - - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "-k": - flags |= PackageManager.DELETE_KEEP_DATA; - break; - case "--user": - userId = UserHandle.parseUserArg(getNextArgRequired()); - break; - case "--versionCode": - versionCode = Integer.parseInt(getNextArgRequired()); - break; - default: - pw.println("Error: Unknown option: " + opt); - return 1; - } - } - - final String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; - } - - // if a split is specified, just remove it and not the whole package - final String splitName = getNextArg(); - if (splitName != null) { - return runRemoveSplit(packageName, splitName); - } - - userId = translateUserId(userId, "runUninstall"); - if (userId == UserHandle.USER_ALL) { - userId = UserHandle.USER_SYSTEM; - flags |= PackageManager.DELETE_ALL_USERS; - } else { - final PackageInfo info = mInterface.getPackageInfo(packageName, - PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId); - if (info == null) { - pw.println("Failure [not installed for " + userId + "]"); - return 1; - } - final boolean isSystem = - (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - // If we are being asked to delete a system app for just one - // user set flag so it disables rather than reverting to system - // version of the app. - if (isSystem) { - flags |= PackageManager.DELETE_SYSTEM_APP; - } - } - - final LocalIntentReceiver receiver = new LocalIntentReceiver(); - mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName, - versionCode), null /*callerPackageName*/, flags, - receiver.getIntentSender(), userId); - - final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); - if (status == PackageInstaller.STATUS_SUCCESS) { - pw.println("Success"); - return 0; - } else { - pw.println("Failure [" - + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); - return 1; - } - } - - private int runRemoveSplit(String packageName, String splitName) throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING); - sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; - sessionParams.appPackageName = packageName; - final int sessionId = - doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL); - boolean abandonSession = true; - try { - if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/) - != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - if (doCommitSession(sessionId, false /*logSuccess*/) - != PackageInstaller.STATUS_SUCCESS) { - return 1; - } - abandonSession = false; - pw.println("Success"); - return 0; - } finally { - if (abandonSession) { - try { - doAbandonSession(sessionId, false /*logSuccess*/); - } catch (Exception ignore) { - } - } - } - } - private Intent parseIntentAndUser() throws URISyntaxException { mTargetUser = UserHandle.USER_CURRENT; mBrief = false; @@ -1154,6 +835,1029 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runInstall() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final InstallParams params = makeInstallParams(); + final String inPath = getNextArg(); + + setParamsSize(params, inPath); + final int sessionId = doCreateSession(params.sessionParams, + params.installerPackageName, params.userId); + boolean abandonSession = true; + try { + if (inPath == null && params.sessionParams.sizeBytes == -1) { + pw.println("Error: must either specify a package size or an APK file"); + return 1; + } + if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk", + false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + if (doCommitSession(sessionId, false /*logSuccess*/) + != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + abandonSession = false; + pw.println("Success"); + return 0; + } finally { + if (abandonSession) { + try { + doAbandonSession(sessionId, false /*logSuccess*/); + } catch (Exception ignore) { + } + } + } + } + + private int runInstallAbandon() throws RemoteException { + final int sessionId = Integer.parseInt(getNextArg()); + return doAbandonSession(sessionId, true /*logSuccess*/); + } + + private int runInstallCommit() throws RemoteException { + final int sessionId = Integer.parseInt(getNextArg()); + return doCommitSession(sessionId, true /*logSuccess*/); + } + + private int runInstallCreate() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final InstallParams installParams = makeInstallParams(); + final int sessionId = doCreateSession(installParams.sessionParams, + installParams.installerPackageName, installParams.userId); + + // NOTE: adb depends on parsing this string + pw.println("Success: created install session [" + sessionId + "]"); + return 0; + } + + private int runInstallWrite() throws RemoteException { + long sizeBytes = -1; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("-S")) { + sizeBytes = Long.parseLong(getNextArg()); + } else { + throw new IllegalArgumentException("Unknown option: " + opt); + } + } + + final int sessionId = Integer.parseInt(getNextArg()); + final String splitName = getNextArg(); + final String path = getNextArg(); + return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/); + } + + private int runInstallRemove() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + + final int sessionId = Integer.parseInt(getNextArg()); + + final String splitName = getNextArg(); + if (splitName == null) { + pw.println("Error: split name not specified"); + return 1; + } + return doRemoveSplit(sessionId, splitName, true /*logSuccess*/); + } + + private int runInstallExisting() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_SYSTEM; + int installFlags = 0; + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + case "--ephemeral": + case "--instant": + installFlags |= PackageManager.INSTALL_INSTANT_APP; + installFlags &= ~PackageManager.INSTALL_FULL_APP; + break; + case "--full": + installFlags &= ~PackageManager.INSTALL_INSTANT_APP; + installFlags |= PackageManager.INSTALL_FULL_APP; + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + final String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + + try { + final int res = mInterface.installExistingPackageAsUser(packageName, userId, + installFlags, PackageManager.INSTALL_REASON_UNKNOWN); + if (res == PackageManager.INSTALL_FAILED_INVALID_URI) { + throw new NameNotFoundException("Package " + packageName + " doesn't exist"); + } + pw.println("Package " + packageName + " installed for user: " + userId); + return 0; + } catch (RemoteException | NameNotFoundException e) { + pw.println(e.toString()); + return 1; + } + } + + private int runSetInstallLocation() throws RemoteException { + int loc; + + String arg = getNextArg(); + if (arg == null) { + getErrPrintWriter().println("Error: no install location specified."); + return 1; + } + try { + loc = Integer.parseInt(arg); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: install location has to be a number."); + return 1; + } + if (!mInterface.setInstallLocation(loc)) { + getErrPrintWriter().println("Error: install location has to be a number."); + return 1; + } + return 0; + } + + private int runGetInstallLocation() throws RemoteException { + int loc = mInterface.getInstallLocation(); + String locStr = "invalid"; + if (loc == PackageHelper.APP_INSTALL_AUTO) { + locStr = "auto"; + } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) { + locStr = "internal"; + } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) { + locStr = "external"; + } + getOutPrintWriter().println(loc + "[" + locStr + "]"); + return 0; + } + + public int runMovePackage() throws RemoteException { + final String packageName = getNextArg(); + if (packageName == null) { + getErrPrintWriter().println("Error: package name not specified"); + return 1; + } + String volumeUuid = getNextArg(); + if ("internal".equals(volumeUuid)) { + volumeUuid = null; + } + + final int moveId = mInterface.movePackage(packageName, volumeUuid); + + int status = mInterface.getMoveStatus(moveId); + while (!PackageManager.isMoveStatusFinished(status)) { + SystemClock.sleep(DateUtils.SECOND_IN_MILLIS); + status = mInterface.getMoveStatus(moveId); + } + + if (status == PackageManager.MOVE_SUCCEEDED) { + getOutPrintWriter().println("Success"); + return 0; + } else { + getErrPrintWriter().println("Failure [" + status + "]"); + return 1; + } + } + + public int runMovePrimaryStorage() throws RemoteException { + String volumeUuid = getNextArg(); + if ("internal".equals(volumeUuid)) { + volumeUuid = null; + } + + final int moveId = mInterface.movePrimaryStorage(volumeUuid); + + int status = mInterface.getMoveStatus(moveId); + while (!PackageManager.isMoveStatusFinished(status)) { + SystemClock.sleep(DateUtils.SECOND_IN_MILLIS); + status = mInterface.getMoveStatus(moveId); + } + + if (status == PackageManager.MOVE_SUCCEEDED) { + getOutPrintWriter().println("Success"); + return 0; + } else { + getErrPrintWriter().println("Failure [" + status + "]"); + return 1; + } + } + + private int runCompile() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false); + boolean forceCompilation = false; + boolean allPackages = false; + boolean clearProfileData = false; + String compilerFilter = null; + String compilationReason = null; + String checkProfilesRaw = null; + boolean secondaryDex = false; + String split = null; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-a": + allPackages = true; + break; + case "-c": + clearProfileData = true; + break; + case "-f": + forceCompilation = true; + break; + case "-m": + compilerFilter = getNextArgRequired(); + break; + case "-r": + compilationReason = getNextArgRequired(); + break; + case "--check-prof": + checkProfilesRaw = getNextArgRequired(); + break; + case "--reset": + forceCompilation = true; + clearProfileData = true; + compilationReason = "install"; + break; + case "--secondary-dex": + secondaryDex = true; + break; + case "--split": + split = getNextArgRequired(); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + if (checkProfilesRaw != null) { + if ("true".equals(checkProfilesRaw)) { + checkProfiles = true; + } else if ("false".equals(checkProfilesRaw)) { + checkProfiles = false; + } else { + pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\"."); + return 1; + } + } + + if (compilerFilter != null && compilationReason != null) { + pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " + + "at the same time"); + return 1; + } + if (compilerFilter == null && compilationReason == null) { + pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " + + "reason (\"-r\") at the same time"); + return 1; + } + + if (allPackages && split != null) { + pw.println("-a cannot be specified together with --split"); + return 1; + } + + if (secondaryDex && split != null) { + pw.println("--secondary-dex cannot be specified together with --split"); + return 1; + } + + String targetCompilerFilter; + if (compilerFilter != null) { + if (!DexFile.isValidCompilerFilter(compilerFilter)) { + pw.println("Error: \"" + compilerFilter + + "\" is not a valid compilation filter."); + return 1; + } + targetCompilerFilter = compilerFilter; + } else { + int reason = -1; + for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { + if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals( + compilationReason)) { + reason = i; + break; + } + } + if (reason == -1) { + pw.println("Error: Unknown compilation reason: " + compilationReason); + return 1; + } + targetCompilerFilter = + PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason); + } + + + List<String> packageNames = null; + if (allPackages) { + packageNames = mInterface.getAllPackages(); + } else { + String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + packageNames = Collections.singletonList(packageName); + } + + List<String> failedPackages = new ArrayList<>(); + for (String packageName : packageNames) { + if (clearProfileData) { + mInterface.clearApplicationProfileData(packageName); + } + + boolean result = secondaryDex + ? mInterface.performDexOptSecondary(packageName, + targetCompilerFilter, forceCompilation) + : mInterface.performDexOptMode(packageName, + checkProfiles, targetCompilerFilter, forceCompilation, + true /* bootComplete */, split); + if (!result) { + failedPackages.add(packageName); + } + } + + if (failedPackages.isEmpty()) { + pw.println("Success"); + return 0; + } else if (failedPackages.size() == 1) { + pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled"); + return 1; + } else { + pw.print("Failure: the following packages could not be compiled: "); + boolean is_first = true; + for (String packageName : failedPackages) { + if (is_first) { + is_first = false; + } else { + pw.print(", "); + } + pw.print(packageName); + } + pw.println(); + return 1; + } + } + + private int runreconcileSecondaryDexFiles() throws RemoteException { + String packageName = getNextArg(); + mInterface.reconcileSecondaryDexFiles(packageName); + return 0; + } + + public int runForceDexOpt() throws RemoteException { + mInterface.forceDexOpt(getNextArgRequired()); + return 0; + } + + private int runDexoptJob() throws RemoteException { + boolean result = mInterface.runBackgroundDexoptJob(); + return result ? 0 : -1; + } + + private int runDumpProfiles() throws RemoteException { + String packageName = getNextArg(); + mInterface.dumpProfiles(packageName); + return 0; + } + + private int runUninstall() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + int flags = 0; + int userId = UserHandle.USER_ALL; + int versionCode = PackageManager.VERSION_CODE_HIGHEST; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-k": + flags |= PackageManager.DELETE_KEEP_DATA; + break; + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + case "--versionCode": + versionCode = Integer.parseInt(getNextArgRequired()); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + final String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + + // if a split is specified, just remove it and not the whole package + final String splitName = getNextArg(); + if (splitName != null) { + return runRemoveSplit(packageName, splitName); + } + + userId = translateUserId(userId, "runUninstall"); + if (userId == UserHandle.USER_ALL) { + userId = UserHandle.USER_SYSTEM; + flags |= PackageManager.DELETE_ALL_USERS; + } else { + final PackageInfo info = mInterface.getPackageInfo(packageName, + PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId); + if (info == null) { + pw.println("Failure [not installed for " + userId + "]"); + return 1; + } + final boolean isSystem = + (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + // If we are being asked to delete a system app for just one + // user set flag so it disables rather than reverting to system + // version of the app. + if (isSystem) { + flags |= PackageManager.DELETE_SYSTEM_APP; + } + } + + final LocalIntentReceiver receiver = new LocalIntentReceiver(); + mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName, + versionCode), null /*callerPackageName*/, flags, + receiver.getIntentSender(), userId); + + final Intent result = receiver.getResult(); + final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + if (status == PackageInstaller.STATUS_SUCCESS) { + pw.println("Success"); + return 0; + } else { + pw.println("Failure [" + + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]"); + return 1; + } + } + + private int runRemoveSplit(String packageName, String splitName) throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING); + sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; + sessionParams.appPackageName = packageName; + final int sessionId = + doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL); + boolean abandonSession = true; + try { + if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/) + != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + if (doCommitSession(sessionId, false /*logSuccess*/) + != PackageInstaller.STATUS_SUCCESS) { + return 1; + } + abandonSession = false; + pw.println("Success"); + return 0; + } finally { + if (abandonSession) { + try { + doAbandonSession(sessionId, false /*logSuccess*/); + } catch (Exception ignore) { + } + } + } + } + + 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(); + } + } + } + + private int runClear() throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return 1; + } + + ClearDataObserver obs = new ClearDataObserver(); + ActivityManager.getService().clearApplicationUserData(pkg, obs, userId); + synchronized (obs) { + while (!obs.finished) { + try { + obs.wait(); + } catch (InterruptedException e) { + } + } + } + + if (obs.result) { + getOutPrintWriter().println("Success"); + return 0; + } else { + getErrPrintWriter().println("Failed"); + return 1; + } + } + + private static String enabledSettingToString(int state) { + switch (state) { + case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: + return "default"; + case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: + return "enabled"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: + return "disabled"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: + return "disabled-user"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: + return "disabled-until-used"; + } + return "unknown"; + } + + private int runSetEnabledSetting(int state) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package or component specified"); + return 1; + } + ComponentName cn = ComponentName.unflattenFromString(pkg); + if (cn == null) { + mInterface.setApplicationEnabledSetting(pkg, state, 0, userId, + "shell:" + android.os.Process.myUid()); + getOutPrintWriter().println("Package " + pkg + " new state: " + + enabledSettingToString( + mInterface.getApplicationEnabledSetting(pkg, userId))); + return 0; + } else { + mInterface.setComponentEnabledSetting(cn, state, 0, userId); + getOutPrintWriter().println("Component " + cn.toShortString() + " new state: " + + enabledSettingToString( + mInterface.getComponentEnabledSetting(cn, userId))); + return 0; + } + } + + private int runSetHiddenSetting(boolean state) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String option = getNextOption(); + if (option != null && option.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package or component specified"); + return 1; + } + mInterface.setApplicationHiddenSettingAsUser(pkg, state, userId); + getOutPrintWriter().println("Package " + pkg + " new hidden state: " + + mInterface.getApplicationHiddenSettingAsUser(pkg, userId)); + return 0; + } + + private int runSuspend(boolean suspendedState) { + final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_SYSTEM; + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + + try { + mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState, + userId); + pw.println("Package " + packageName + " new suspended state: " + + mInterface.isPackageSuspendedForUser(packageName, userId)); + return 0; + } catch (RemoteException | IllegalArgumentException e) { + pw.println(e.toString()); + return 1; + } + } + + private int runGrantRevokePermission(boolean grant) throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + + String opt = null; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + } + + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return 1; + } + String perm = getNextArg(); + if (perm == null) { + getErrPrintWriter().println("Error: no permission specified"); + return 1; + } + + if (grant) { + mInterface.grantRuntimePermission(pkg, perm, userId); + } else { + mInterface.revokeRuntimePermission(pkg, perm, userId); + } + return 0; + } + + private int runResetPermissions() throws RemoteException { + mInterface.resetRuntimePermissions(); + return 0; + } + + private int runSetPermissionEnforced() throws RemoteException { + final String permission = getNextArg(); + if (permission == null) { + getErrPrintWriter().println("Error: no permission specified"); + return 1; + } + final String enforcedRaw = getNextArg(); + if (enforcedRaw == null) { + getErrPrintWriter().println("Error: no enforcement specified"); + return 1; + } + mInterface.setPermissionEnforced(permission, Boolean.parseBoolean(enforcedRaw)); + return 0; + } + + private int runGetPrivappPermissions() { + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg); + getOutPrintWriter().println(privAppPermissions == null + ? "{}" : privAppPermissions.toString()); + return 0; + } + + private int runGetPrivappDenyPermissions() { + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + ArraySet<String> privAppDenyPermissions = + SystemConfig.getInstance().getPrivAppDenyPermissions(pkg); + getOutPrintWriter().println(privAppDenyPermissions == null + ? "{}" : privAppDenyPermissions.toString()); + return 0; + } + + private int runGetOemPermissions() { + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + final Map<String, Boolean> oemPermissions = SystemConfig.getInstance() + .getOemPermissions(pkg); + if (oemPermissions == null || oemPermissions.isEmpty()) { + getOutPrintWriter().println("{}"); + } else { + oemPermissions.forEach((permission, granted) -> + getOutPrintWriter().println(permission + " granted:" + granted) + ); + } + return 0; + } + + private String linkStateToString(int state) { + switch (state) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never"; + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask"; + } + return "Unknown link state: " + state; + } + + // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined} + private int runSetAppLink() throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: unknown option: " + opt); + return 1; + } + } + + // Package name to act on; required + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + + // State to apply; {always|ask|never|undefined}, required + final String modeString = getNextArg(); + if (modeString == null) { + getErrPrintWriter().println("Error: no app link state specified."); + return 1; + } + + final int newMode; + switch (modeString.toLowerCase()) { + case "undefined": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + break; + + case "always": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; + break; + + case "ask": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; + break; + + case "always-ask": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK; + break; + + case "never": + newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; + break; + + default: + getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'"); + return 1; + } + + final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId); + if (info == null) { + getErrPrintWriter().println("Error: package " + pkg + " not found."); + return 1; + } + + if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) { + getErrPrintWriter().println("Error: package " + pkg + " does not handle web links."); + return 1; + } + + if (!mInterface.updateIntentVerificationStatus(pkg, newMode, userId)) { + getErrPrintWriter().println("Error: unable to update app link status for " + pkg); + return 1; + } + + return 0; + } + + // pm get-app-link [--user USER_ID] PACKAGE + private int runGetAppLink() throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: unknown option: " + opt); + return 1; + } + } + + // Package name to act on; required + final String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified."); + return 1; + } + + final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId); + if (info == null) { + getErrPrintWriter().println("Error: package " + pkg + " not found."); + return 1; + } + + if ((info.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) { + getErrPrintWriter().println("Error: package " + pkg + " does not handle web links."); + return 1; + } + + getOutPrintWriter().println(linkStateToString( + mInterface.getIntentVerificationStatus(pkg, userId))); + + return 0; + } + + private int runTrimCaches() throws RemoteException { + String size = getNextArg(); + if (size == null) { + getErrPrintWriter().println("Error: no size specified"); + return 1; + } + long multiplier = 1; + int len = size.length(); + char c = size.charAt(len - 1); + if (c < '0' || c > '9') { + if (c == 'K' || c == 'k') { + multiplier = 1024L; + } else if (c == 'M' || c == 'm') { + multiplier = 1024L*1024L; + } else if (c == 'G' || c == 'g') { + multiplier = 1024L*1024L*1024L; + } else { + getErrPrintWriter().println("Invalid suffix: " + c); + return 1; + } + size = size.substring(0, len-1); + } + long sizeVal; + try { + sizeVal = Long.parseLong(size) * multiplier; + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: expected number at: " + size); + return 1; + } + String volumeUuid = getNextArg(); + if ("internal".equals(volumeUuid)) { + volumeUuid = null; + } + ClearDataObserver obs = new ClearDataObserver(); + mInterface.freeStorageAndNotify(volumeUuid, sizeVal, + StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs); + synchronized (obs) { + while (!obs.finished) { + try { + obs.wait(); + } catch (InterruptedException e) { + } + } + } + return 0; + } + + private static boolean isNumber(String s) { + try { + Integer.parseInt(s); + } catch (NumberFormatException nfe) { + return false; + } + return true; + } + + public int runCreateUser() throws RemoteException { + String name; + int userId = -1; + int flags = 0; + String opt; + while ((opt = getNextOption()) != null) { + if ("--profileOf".equals(opt)) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else if ("--managed".equals(opt)) { + flags |= UserInfo.FLAG_MANAGED_PROFILE; + } else if ("--restricted".equals(opt)) { + flags |= UserInfo.FLAG_RESTRICTED; + } else if ("--ephemeral".equals(opt)) { + flags |= UserInfo.FLAG_EPHEMERAL; + } else if ("--guest".equals(opt)) { + flags |= UserInfo.FLAG_GUEST; + } else if ("--demo".equals(opt)) { + flags |= UserInfo.FLAG_DEMO; + } else { + getErrPrintWriter().println("Error: unknown option " + opt); + return 1; + } + } + String arg = getNextArg(); + if (arg == null) { + getErrPrintWriter().println("Error: no user name specified."); + return 1; + } + name = arg; + UserInfo info; + IUserManager um = IUserManager.Stub.asInterface( + ServiceManager.getService(Context.USER_SERVICE)); + IAccountManager accm = IAccountManager.Stub.asInterface( + ServiceManager.getService(Context.ACCOUNT_SERVICE)); + if ((flags & UserInfo.FLAG_RESTRICTED) != 0) { + // In non-split user mode, userId can only be SYSTEM + int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM; + info = um.createRestrictedProfile(name, parentUserId); + accm.addSharedAccountsFromParentUser(parentUserId, userId, + (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell"); + } else if (userId < 0) { + info = um.createUser(name, flags); + } else { + info = um.createProfileForUser(name, flags, userId, null); + } + + if (info != null) { + getOutPrintWriter().println("Success: created user id " + info.id); + return 0; + } else { + getErrPrintWriter().println("Error: couldn't create User."); + return 1; + } + } + + public int runRemoveUser() throws RemoteException { + int userId; + String arg = getNextArg(); + if (arg == null) { + getErrPrintWriter().println("Error: no user id specified."); + return 1; + } + userId = UserHandle.parseUserArg(arg); + IUserManager um = IUserManager.Stub.asInterface( + ServiceManager.getService(Context.USER_SERVICE)); + if (um.removeUser(userId)) { + getOutPrintWriter().println("Success: removed user"); + return 0; + } else { + getErrPrintWriter().println("Error: couldn't remove user id " + userId); + return 1; + } + } + + public int runSetUserRestriction() throws RemoteException { + int userId = UserHandle.USER_SYSTEM; + String opt = getNextOption(); + if (opt != null && "--user".equals(opt)) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } + + String restriction = getNextArg(); + String arg = getNextArg(); + boolean value; + if ("1".equals(arg)) { + value = true; + } else if ("0".equals(arg)) { + value = false; + } else { + getErrPrintWriter().println("Error: valid value not specified"); + return 1; + } + IUserManager um = IUserManager.Stub.asInterface( + ServiceManager.getService(Context.USER_SERVICE)); + um.setUserRestriction(restriction, value, userId); + return 0; + } + + public int runGetMaxUsers() { + getOutPrintWriter().println("Maximum supported users: " + + UserManager.getMaxSupportedUsers()); + return 0; + } + private static class InstallParams { SessionParams sessionParams; String installerPackageName; @@ -1287,46 +1991,17 @@ class PackageManagerShellCommand extends ShellCommand { } } - private int runGetPrivappPermissions() { - final String pkg = getNextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); - return 1; - } - ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg); - getOutPrintWriter().println(privAppPermissions == null - ? "{}" : privAppPermissions.toString()); - return 0; - } + private int runSetInstaller() throws RemoteException { + final String targetPackage = getNextArg(); + final String installerPackageName = getNextArg(); - private int runGetPrivappDenyPermissions() { - final String pkg = getNextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); + if (targetPackage == null || installerPackageName == null) { + getErrPrintWriter().println("Must provide both target and installer package names"); return 1; } - ArraySet<String> privAppDenyPermissions = - SystemConfig.getInstance().getPrivAppDenyPermissions(pkg); - getOutPrintWriter().println(privAppDenyPermissions == null - ? "{}" : privAppDenyPermissions.toString()); - return 0; - } - private int runGetOemPermissions() { - final String pkg = getNextArg(); - if (pkg == null) { - System.err.println("Error: no package specified."); - return 1; - } - final Map<String, Boolean> oemPermissions = SystemConfig.getInstance() - .getOemPermissions(pkg); - if (oemPermissions == null || oemPermissions.isEmpty()) { - getOutPrintWriter().println("{}"); - } else { - oemPermissions.forEach((permission, granted) -> - getOutPrintWriter().println(permission + " granted:" + granted) - ); - } + mInterface.setInstallerPackageName(targetPackage, installerPackageName); + getOutPrintWriter().println("Success"); return 0; } @@ -1367,6 +2042,16 @@ class PackageManagerShellCommand extends ShellCommand { } } + private int runDump() { + String pkg = getNextArg(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return 1; + } + ActivityManager.dumpPackageStateStatic(getOutFileDescriptor(), pkg); + return 0; + } + private static String checkAbiArgument(String abi) { if (TextUtils.isEmpty(abi)) { throw new IllegalArgumentException("Missing ABI argument"); @@ -1663,52 +2348,31 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(""); - pw.println(" compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]"); - pw.println(" [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)"); - pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\"."); - pw.println(" Options:"); - pw.println(" -a: compile all packages"); - pw.println(" -c: clear profile data before compiling"); - pw.println(" -f: force compilation even if not needed"); - pw.println(" -m: select compilation mode"); - pw.println(" MODE is one of the dex2oat compiler filters:"); - pw.println(" assume-verified"); - pw.println(" extract"); - pw.println(" verify"); - pw.println(" quicken"); - pw.println(" space-profile"); - pw.println(" space"); - pw.println(" speed-profile"); - pw.println(" speed"); - pw.println(" everything"); - pw.println(" -r: select compilation reason"); - pw.println(" REASON is one of:"); - for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { - pw.println(" " + PackageManagerServiceCompilerMapping.REASON_STRINGS[i]); - } - pw.println(" --reset: restore package to its post-install state"); - pw.println(" --check-prof (true | false): look at profiles when doing dexopt?"); - pw.println(" --secondary-dex: compile app secondary dex files"); - pw.println(" --split SPLIT: compile only the given split name"); - pw.println(" bg-dexopt-job"); - pw.println(" Execute the background optimizations immediately."); - pw.println(" Note that the command only runs the background optimizer logic. It may"); - pw.println(" overlap with the actual job but the job scheduler will not be able to"); - pw.println(" cancel it. It will also run even if the device is not in the idle"); - pw.println(" maintenance mode."); + pw.println(" path [--user USER_ID] PACKAGE"); + pw.println(" Print the path to the .apk of the given PACKAGE."); + pw.println(""); + pw.println(" dump PACKAGE"); + pw.println(" Print various system state associated with the given PACKAGE."); + pw.println(""); pw.println(" list features"); pw.println(" Prints all features of the system."); + pw.println(""); + pw.println(" has-feature FEATURE_NAME [version]"); + pw.println(" Prints true and returns exit status 0 when system has a FEATURE_NAME,"); + pw.println(" otherwise prints false and returns exit status 1"); + pw.println(""); pw.println(" list instrumentation [-f] [TARGET-PACKAGE]"); pw.println(" Prints all test packages; optionally only those targeting TARGET-PACKAGE"); pw.println(" Options:"); pw.println(" -f: dump the name of the .apk file containing the test package"); + pw.println(""); pw.println(" list libraries"); pw.println(" Prints all system libraries."); - pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] " - + "[--uid UID] [--user USER_ID] [FILTER]"); + pw.println(""); + pw.println(" list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] "); + pw.println(" [--uid UID] [--user USER_ID] [FILTER]"); pw.println(" Prints all packages; optionally only those whose name contains"); - pw.println(" the text in FILTER."); - pw.println(" Options:"); + pw.println(" the text in FILTER. Options are:"); pw.println(" -f: see their associated file"); pw.println(" -d: filter to only show disabled packages"); pw.println(" -e: filter to only show enabled packages"); @@ -1720,42 +2384,216 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -u: also include uninstalled packages"); pw.println(" --uid UID: filter to only show packages with the given UID"); pw.println(" --user USER_ID: only list packages belonging to the given user"); - pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE"); - pw.println(" Reconciles the package secondary dex files with the generated oat files."); + pw.println(""); pw.println(" list permission-groups"); pw.println(" Prints all known permission groups."); + pw.println(""); pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]"); - pw.println(" Prints all known permissions; optionally only those in GROUP."); - pw.println(" Options:"); + pw.println(" Prints all known permissions; optionally only those in GROUP. Options are:"); pw.println(" -g: organize by group"); pw.println(" -f: print all information"); pw.println(" -s: short summary"); pw.println(" -d: only list dangerous permissions"); pw.println(" -u: list only the permissions users will see"); - pw.println(" dump-profiles TARGET-PACKAGE"); - pw.println(" Dumps method/class profile files to"); - pw.println(" /data/misc/profman/TARGET-PACKAGE.txt"); + pw.println(""); pw.println(" resolve-activity [--brief] [--components] [--user USER_ID] INTENT"); - pw.println(" Prints the activity that resolves to the given Intent."); + pw.println(" Prints the activity that resolves to the given INTENT."); + pw.println(""); pw.println(" query-activities [--brief] [--components] [--user USER_ID] INTENT"); - pw.println(" Prints all activities that can handle the given Intent."); + pw.println(" Prints all activities that can handle the given INTENT."); + pw.println(""); pw.println(" query-services [--brief] [--components] [--user USER_ID] INTENT"); - pw.println(" Prints all services that can handle the given Intent."); + pw.println(" Prints all services that can handle the given INTENT."); + pw.println(""); pw.println(" query-receivers [--brief] [--components] [--user USER_ID] INTENT"); - pw.println(" Prints all broadcast receivers that can handle the given Intent."); + pw.println(" Prints all broadcast receivers that can handle the given INTENT."); + pw.println(""); + pw.println(" install [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]"); + pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); + pw.println(" [--originating-uri URI] [---referrer URI]"); + pw.println(" [--abi ABI_NAME] [--force-sdk]"); + pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); + pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]"); + pw.println(" Install an application. Must provide the apk data to install, either as a"); + pw.println(" file path or '-' to read from stdin. Options are:"); + pw.println(" -l: forward lock application"); + pw.println(" -r: allow replacement of existing application"); + pw.println(" -t: allow test packages"); + pw.println(" -i: specify package name of installer owning the app"); + pw.println(" -s: install application on sdcard"); + pw.println(" -f: install application on internal flash"); + pw.println(" -d: allow version code downgrade (debuggable packages only)"); + pw.println(" -p: partial application install (new split on top of existing pkg)"); + pw.println(" -g: grant all runtime permissions"); + pw.println(" -S: size in bytes of package, required for stdin"); + pw.println(" --user: install under the given user."); + pw.println(" --dont-kill: installing a new feature split, don't kill running app"); + pw.println(" --originating-uri: set URI where app was downloaded from"); + pw.println(" --referrer: set URI that instigated the install of the app"); + pw.println(" --pkg: specify expected package name of app being installed"); + pw.println(" --abi: override the default ABI of the platform"); + pw.println(" --instantapp: cause the app to be installed as an ephemeral install app"); + pw.println(" --full: cause the app to be installed as a non-ephemeral full app"); + pw.println(" --install-location: force the install location:"); + pw.println(" 0=auto, 1=internal only, 2=prefer external"); + pw.println(" --force-uuid: force install on to disk volume with given UUID"); + pw.println(" --force-sdk: allow install even when existing app targets platform"); + pw.println(" codename but new one targets a final API level"); + pw.println(""); + pw.println(" install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]"); + pw.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]"); + pw.println(" [--originating-uri URI] [---referrer URI]"); + pw.println(" [--abi ABI_NAME] [--force-sdk]"); + pw.println(" [--preload] [--instantapp] [--full] [--dont-kill]"); + pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); + pw.println(" Like \"install\", but starts an install session. Use \"install-write\""); + pw.println(" to push data into the session, and \"install-commit\" to finish."); + pw.println(""); + pw.println(" install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]"); + pw.println(" Write an apk into the given install session. If the path is '-', data"); + pw.println(" will be read from stdin. Options are:"); + pw.println(" -S: size in bytes of package, required for stdin"); + pw.println(""); + pw.println(" install-commit SESSION_ID"); + pw.println(" Commit the given active install session, installing the app."); + pw.println(""); + pw.println(" install-abandon SESSION_ID"); + pw.println(" Delete the given active install session."); + pw.println(""); + pw.println(" set-install-location LOCATION"); + pw.println(" Changes the default install location. NOTE this is only intended for debugging;"); + pw.println(" using this can cause applications to break and other undersireable behavior."); + pw.println(" LOCATION is one of:"); + pw.println(" 0 [auto]: Let system decide the best location"); + pw.println(" 1 [internal]: Install on internal device storage"); + pw.println(" 2 [external]: Install on external media"); + pw.println(""); + pw.println(" get-install-location"); + pw.println(" Returns the current install location: 0, 1 or 2 as per set-install-location."); + pw.println(""); + pw.println(" move-package PACKAGE [internal|UUID]"); + pw.println(""); + pw.println(" move-primary-storage [internal|UUID]"); + pw.println(""); + pw.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE [SPLIT]"); + pw.println(" Remove the given package name from the system. May remove an entire app"); + pw.println(" if no SPLIT name is specified, otherwise will remove only the split of the"); + pw.println(" given app. Options are:"); + pw.println(" -k: keep the data and cache directories around after package removal."); + pw.println(" --user: remove the app from the given user."); + pw.println(" --versionCode: only uninstall if the app has the given version code."); + pw.println(""); + pw.println(" clear [--user USER_ID] PACKAGE"); + pw.println(" Deletes all data associated with a package."); + pw.println(""); + pw.println(" enable [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" disable [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" disable-user [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" default-state [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" These commands change the enabled state of a given package or"); + pw.println(" component (written as \"package/class\")."); + pw.println(""); + pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT"); + pw.println(""); pw.println(" suspend [--user USER_ID] TARGET-PACKAGE"); pw.println(" Suspends the specified package (as user)."); + pw.println(""); pw.println(" unsuspend [--user USER_ID] TARGET-PACKAGE"); pw.println(" Unsuspends the specified package (as user)."); - pw.println(" set-home-activity [--user USER_ID] TARGET-COMPONENT"); - pw.println(" set the default home activity (aka launcher)."); - pw.println(" has-feature FEATURE_NAME [version]"); - pw.println(" prints true and returns exit status 0 when system has a FEATURE_NAME,"); - pw.println(" otherwise prints false and returns exit status 1"); - pw.println(" get-privileged-permissions TARGET-PACKAGE"); - pw.println(" prints all privileged permissions for a package."); + pw.println(""); + pw.println(" grant [--user USER_ID] PACKAGE PERMISSION"); + pw.println(" revoke [--user USER_ID] PACKAGE PERMISSION"); + pw.println(" These commands either grant or revoke permissions to apps. The permissions"); + pw.println(" must be declared as used in the app's manifest, be runtime permissions"); + pw.println(" (protection level dangerous), and the app targeting SDK greater than Lollipop MR1."); + pw.println(""); + pw.println(" reset-permissions"); + pw.println(" Revert all runtime permissions to their default state."); + pw.println(""); + pw.println(" set-permission-enforced PERMISSION [true|false]"); + pw.println(""); + pw.println(" get-privapp-permissions TARGET-PACKAGE"); + pw.println(" Prints all privileged permissions for a package."); + pw.println(""); + pw.println(" get-privapp-deny-permissions TARGET-PACKAGE"); + pw.println(" Prints all privileged permissions that are denied for a package."); + pw.println(""); pw.println(" get-oem-permissions TARGET-PACKAGE"); - pw.println(" prints all OEM permissions for a package."); + pw.println(" Prints all OEM permissions for a package."); + pw.println(""); + pw.println(" set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}"); + pw.println(" get-app-link [--user USER_ID] PACKAGE"); + pw.println(""); + pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]"); + pw.println(" Trim cache files to reach the given free space."); + pw.println(""); + pw.println(" create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]"); + pw.println(" [--guest] USER_NAME"); + pw.println(" Create a new user with the given USER_NAME, printing the new user identifier"); + pw.println(" of the user."); + pw.println(""); + pw.println(" remove-user USER_ID"); + pw.println(" Remove the user with the given USER_IDENTIFIER, deleting all data"); + pw.println(" associated with that user"); + pw.println(""); + pw.println(" set-user-restriction [--user USER_ID] RESTRICTION VALUE"); + pw.println(""); + pw.println(" get-max-users"); + pw.println(""); + pw.println(" compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]"); + pw.println(" [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)"); + pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\". Options are:"); + pw.println(" -a: compile all packages"); + pw.println(" -c: clear profile data before compiling"); + pw.println(" -f: force compilation even if not needed"); + pw.println(" -m: select compilation mode"); + pw.println(" MODE is one of the dex2oat compiler filters:"); + pw.println(" assume-verified"); + pw.println(" extract"); + pw.println(" verify"); + pw.println(" quicken"); + pw.println(" space-profile"); + pw.println(" space"); + pw.println(" speed-profile"); + pw.println(" speed"); + pw.println(" everything"); + pw.println(" -r: select compilation reason"); + pw.println(" REASON is one of:"); + for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { + pw.println(" " + PackageManagerServiceCompilerMapping.REASON_STRINGS[i]); + } + pw.println(" --reset: restore package to its post-install state"); + pw.println(" --check-prof (true | false): look at profiles when doing dexopt?"); + pw.println(" --secondary-dex: compile app secondary dex files"); + pw.println(" --split SPLIT: compile only the given split name"); + pw.println(""); + pw.println(" force-dex-opt PACKAGE"); + pw.println(" Force immediate execution of dex opt for the given PACKAGE."); + pw.println(""); + pw.println(" bg-dexopt-job"); + pw.println(" Execute the background optimizations immediately."); + pw.println(" Note that the command only runs the background optimizer logic. It may"); + pw.println(" overlap with the actual job but the job scheduler will not be able to"); + pw.println(" cancel it. It will also run even if the device is not in the idle"); + pw.println(" maintenance mode."); + pw.println(""); + pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE"); + pw.println(" Reconciles the package secondary dex files with the generated oat files."); + pw.println(""); + pw.println(" dump-profiles TARGET-PACKAGE"); + pw.println(" Dumps method/class profile files to"); + pw.println(" /data/misc/profman/TARGET-PACKAGE.txt"); + pw.println(""); + pw.println(" set-home-activity [--user USER_ID] TARGET-COMPONENT"); + pw.println(" Set the default home activity (aka launcher)."); + pw.println(""); + pw.println(" set-installer PACKAGE INSTALLER"); + pw.println(" Set installer package name"); + pw.println(""); + pw.println(" get-instantapp-resolver"); + pw.println(" Return the name of the component that is the current instant app installer."); pw.println(); Intent.printIntentArgsHelp(pw , ""); } diff --git a/com/android/server/pm/PackageSetting.java b/com/android/server/pm/PackageSetting.java index 83cb2db2..3b414e9a 100644 --- a/com/android/server/pm/PackageSetting.java +++ b/com/android/server/pm/PackageSetting.java @@ -86,6 +86,10 @@ public final class PackageSetting extends PackageSettingBase { return sharedUserId; } + public SharedUserSetting getSharedUser() { + return sharedUser; + } + @Override public String toString() { return "PackageSetting{" @@ -120,6 +124,14 @@ public final class PackageSetting extends PackageSettingBase { return appId; } + public void setInstallPermissionsFixed(boolean fixed) { + installPermissionsFixed = fixed; + } + + public boolean areInstallPermissionsFixed() { + return installPermissionsFixed; + } + public boolean isPrivileged() { return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } @@ -136,6 +148,10 @@ public final class PackageSetting extends PackageSettingBase { return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0; } + public boolean isUpdatedSystem() { + return (pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + @Override public boolean isSharedUser() { return sharedUser != null; diff --git a/com/android/server/pm/PackageSettingBase.java b/com/android/server/pm/PackageSettingBase.java index e19e83fc..a8387684 100644 --- a/com/android/server/pm/PackageSettingBase.java +++ b/com/android/server/pm/PackageSettingBase.java @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageUserState; +import android.content.pm.Signature; import android.service.pm.PackageProto; import android.util.ArraySet; import android.util.SparseArray; @@ -57,7 +58,7 @@ public abstract class PackageSettingBase extends SettingBase { static final int PKG_INSTALL_COMPLETE = 1; static final int PKG_INSTALL_INCOMPLETE = 0; - final String name; + public final String name; final String realName; String parentPackageName; @@ -231,6 +232,11 @@ public abstract class PackageSettingBase extends SettingBase { public boolean isSharedUser() { return false; } + + public Signature[] getSignatures() { + return signatures.mSignatures; + } + /** * Makes a shallow copy of the given package settings. * diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java index 191b43a6..7077fd16 100644 --- a/com/android/server/pm/Settings.java +++ b/com/android/server/pm/Settings.java @@ -544,7 +544,7 @@ public final class Settings { } final PackageSetting dp = mDisabledSysPackages.get(name); // always make sure the system package code and resource paths dont change - if (dp == null && p.pkg != null && p.pkg.isSystemApp() && !p.pkg.isUpdatedSystemApp()) { + if (dp == null && p.pkg != null && p.pkg.isSystem() && !p.pkg.isUpdatedSystemApp()) { if((p.pkg != null) && (p.pkg.applicationInfo != null)) { p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } diff --git a/com/android/server/pm/UserDataPreparer.java b/com/android/server/pm/UserDataPreparer.java index b8b00af4..bfe09b8e 100644 --- a/com/android/server/pm/UserDataPreparer.java +++ b/com/android/server/pm/UserDataPreparer.java @@ -132,11 +132,9 @@ class UserDataPreparer { if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) { FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId)); FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId)); - FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId)); } if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) { FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId)); - FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId)); } } diff --git a/com/android/server/pm/UserRestrictionsUtils.java b/com/android/server/pm/UserRestrictionsUtils.java index c18a71d3..c86122f2 100644 --- a/com/android/server/pm/UserRestrictionsUtils.java +++ b/com/android/server/pm/UserRestrictionsUtils.java @@ -80,6 +80,7 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, UserManager.DISALLOW_DEBUGGING_FEATURES, UserManager.DISALLOW_CONFIG_VPN, + UserManager.DISALLOW_CONFIG_DATE_TIME, UserManager.DISALLOW_CONFIG_TETHERING, UserManager.DISALLOW_NETWORK_RESET, UserManager.DISALLOW_FACTORY_RESET, @@ -157,6 +158,7 @@ public class UserRestrictionsUtils { private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet( UserManager.DISALLOW_ADJUST_VOLUME, UserManager.DISALLOW_BLUETOOTH_SHARING, + UserManager.DISALLOW_CONFIG_DATE_TIME, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, UserManager.DISALLOW_RUN_IN_BACKGROUND, UserManager.DISALLOW_UNMUTE_MICROPHONE, diff --git a/com/android/server/pm/dex/DexoptOptions.java b/com/android/server/pm/dex/DexoptOptions.java index 4fa47b5d..0966770d 100644 --- a/com/android/server/pm/dex/DexoptOptions.java +++ b/com/android/server/pm/dex/DexoptOptions.java @@ -56,6 +56,9 @@ public final class DexoptOptions { // actually shared at runtime. public static final int DEXOPT_AS_SHARED_LIBRARY = 1 << 6; + // When set, indicates that dexopt is invoked from the background service. + public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9; + // The name of package to optimize. private final String mPackageName; @@ -86,7 +89,8 @@ public final class DexoptOptions { DEXOPT_ONLY_SECONDARY_DEX | DEXOPT_ONLY_SHARED_DEX | DEXOPT_DOWNGRADE | - DEXOPT_AS_SHARED_LIBRARY; + DEXOPT_AS_SHARED_LIBRARY | + DEXOPT_IDLE_BACKGROUND_JOB; if ((flags & (~validityMask)) != 0) { throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags)); } @@ -133,6 +137,10 @@ public final class DexoptOptions { return (mFlags & DEXOPT_AS_SHARED_LIBRARY) != 0; } + public boolean isDexoptIdleBackgroundJob() { + return (mFlags & DEXOPT_IDLE_BACKGROUND_JOB) != 0; + } + public String getSplitName() { return mSplitName; } diff --git a/com/android/server/pm/permission/BasePermission.java b/com/android/server/pm/permission/BasePermission.java index 71d3202d..8c86db64 100644 --- a/com/android/server/pm/permission/BasePermission.java +++ b/com/android/server/pm/permission/BasePermission.java @@ -32,6 +32,7 @@ import android.annotation.Nullable; import android.content.pm.PackageParser; import android.content.pm.PackageParser.Permission; import android.content.pm.PermissionInfo; +import android.content.pm.Signature; import android.os.UserHandle; import android.util.Log; import android.util.Slog; @@ -129,6 +130,9 @@ public final class BasePermission { public PackageSettingBase getSourcePackageSetting() { return sourcePackageSetting; } + public Signature[] getSourceSignatures() { + return sourcePackageSetting.getSignatures(); + } public int getType() { return type; } @@ -277,8 +281,8 @@ public final class BasePermission { // Allow system apps to redefine non-system permissions if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) { final boolean currentOwnerIsSystem = (bp.perm != null - && bp.perm.owner.isSystemApp()); - if (p.owner.isSystemApp()) { + && bp.perm.owner.isSystem()); + if (p.owner.isSystem()) { if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) { // It's a built-in permission and no owner, take ownership now bp.sourcePackageSetting = pkgSetting; diff --git a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 161efd38..533b2619 100644 --- a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -985,7 +985,7 @@ public final class DefaultPermissionGrantPolicy { private PackageParser.Package getSystemPackage(String packageName) { PackageParser.Package pkg = getPackage(packageName); - if (pkg != null && pkg.isSystemApp()) { + if (pkg != null && pkg.isSystem()) { return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null; } return null; @@ -1094,7 +1094,7 @@ public final class DefaultPermissionGrantPolicy { if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) { return true; } - if (!pkg.isPrivilegedApp()) { + if (!pkg.isPrivileged()) { return false; } final PackageParser.Package disabledPkg = diff --git a/com/android/server/pm/permission/PermissionManagerInternal.java b/com/android/server/pm/permission/PermissionManagerInternal.java index 8aac52ae..60c118b5 100644 --- a/com/android/server/pm/permission/PermissionManagerInternal.java +++ b/com/android/server/pm/permission/PermissionManagerInternal.java @@ -19,6 +19,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageParser; +import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.PermissionInfoFlags; @@ -57,7 +58,7 @@ public abstract class PermissionManagerInternal { } public void onInstallPermissionRevoked() { } - public void onPermissionUpdated(int userId) { + public void onPermissionUpdated(int[] updatedUserIds, boolean sync) { } public void onPermissionRemoved() { } @@ -65,6 +66,10 @@ public abstract class PermissionManagerInternal { } } + public abstract void systemReady(); + + public abstract boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId); + public abstract void grantRuntimePermission( @NonNull String permName, @NonNull String packageName, boolean overridePolicy, int callingUid, int userId, @Nullable PermissionCallback callback); @@ -78,9 +83,12 @@ public abstract class PermissionManagerInternal { public abstract void revokeRuntimePermission(@NonNull String permName, @NonNull String packageName, boolean overridePolicy, int callingUid, int userId, @Nullable PermissionCallback callback); - public abstract int[] revokeUnusedSharedUserPermissions(@NonNull SharedUserSetting suSetting, - @NonNull int[] allUserIds); + public abstract void updatePermissions(@Nullable String packageName, + @Nullable PackageParser.Package pkg, boolean replaceGrant, + @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback); + public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated, + @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback); /** * Add all permissions in the given package. @@ -89,22 +97,28 @@ public abstract class PermissionManagerInternal { * the permission settings. */ public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty); + public abstract void addAllPermissionGroups(@NonNull PackageParser.Package pkg, boolean chatty); public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty); public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async, int callingUid, @Nullable PermissionCallback callback); public abstract void removeDynamicPermission(@NonNull String permName, int callingUid, @Nullable PermissionCallback callback); - public abstract int updatePermissions(@Nullable String changingPkg, - @Nullable PackageParser.Package pkgInfo, int flags); - public abstract int updatePermissionTrees(@Nullable String changingPkg, - @Nullable PackageParser.Package pkgInfo, int flags); - public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName); public abstract int getPermissionFlags(@NonNull String permName, @NonNull String packageName, int callingUid, int userId); /** + * Retrieve all of the information we know about a particular group of permissions. + */ + public abstract @Nullable PermissionGroupInfo getPermissionGroupInfo( + @NonNull String groupName, int flags, int callingUid); + /** + * Retrieve all of the known permission groups in the system. + */ + public abstract @Nullable List<PermissionGroupInfo> getAllPermissionGroups(int flags, + int callingUid); + /** * Retrieve all of the information we know about a particular permission. */ public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName, @@ -132,6 +146,7 @@ public abstract class PermissionManagerInternal { public abstract int checkPermission(@NonNull String permName, @NonNull String packageName, int callingUid, int userId); + public abstract int checkUidPermission(String permName, int uid, int callingUid); /** * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS @@ -147,8 +162,5 @@ public abstract class PermissionManagerInternal { public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy(); /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */ - public abstract Iterator<BasePermission> getPermissionIteratorTEMP(); public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName); - public abstract void putPermissionTEMP(@NonNull String permName, - @NonNull BasePermission permission); }
\ No newline at end of file diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java index d2d857ca..76805ce3 100644 --- a/com/android/server/pm/permission/PermissionManagerService.java +++ b/com/android/server/pm/permission/PermissionManagerService.java @@ -18,6 +18,13 @@ package com.android.server.pm.permission; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; +import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; +import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING; +import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS; +import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; +import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.Manifest; import android.annotation.NonNull; @@ -27,7 +34,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; -import android.content.pm.ParceledListSlice; +import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.PackageParser.Package; import android.os.Binder; @@ -35,18 +42,24 @@ import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; +import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.os.RoSystemProperties; import com.android.internal.util.ArrayUtils; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -58,6 +71,7 @@ import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.PackageSetting; import com.android.server.pm.ProcessLoggingHandler; import com.android.server.pm.SharedUserSetting; +import com.android.server.pm.UserManagerService; import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback; import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback; import com.android.server.pm.permission.PermissionsState.PermissionState; @@ -69,6 +83,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -107,8 +122,19 @@ public class PermissionManagerService { Manifest.permission.READ_PHONE_NUMBERS, Manifest.permission.ANSWER_PHONE_CALLS); - /** Cap the size of permission trees that 3rd party apps can define */ - private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; // characters of text + /** Permission grant: not grant the permission. */ + private static final int GRANT_DENIED = 1; + /** Permission grant: grant the permission as an install permission. */ + private static final int GRANT_INSTALL = 2; + /** Permission grant: grant the permission as a runtime one. */ + private static final int GRANT_RUNTIME = 3; + /** Permission grant: grant as runtime a permission that was granted as an install time one. */ + private static final int GRANT_UPGRADE = 4; + + /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ + private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; + /** Empty array to avoid allocations */ + private static final int[] EMPTY_INT_ARRAY = new int[0]; /** Lock to protect internal data access */ private final Object mLock; @@ -122,13 +148,29 @@ public class PermissionManagerService { /** Default permission policy to provide proper behaviour out-of-the-box */ private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy; - /** Internal storage for permissions and related settings */ - private final PermissionSettings mSettings; + /** + * Built-in permissions. Read from system configuration files. Mapping is from + * UID to permission name. + */ + private final SparseArray<ArraySet<String>> mSystemPermissions; + + /** Built-in group IDs given to all packages. Read from system configuration files. */ + private final int[] mGlobalGids; private final HandlerThread mHandlerThread; private final Handler mHandler; private final Context mContext; + /** Internal storage for permissions and related settings */ + @GuardedBy("mLock") + private final PermissionSettings mSettings; + + @GuardedBy("mLock") + private ArraySet<String> mPrivappPermissionsViolations; + + @GuardedBy("mLock") + private boolean mSystemReady; + PermissionManagerService(Context context, @Nullable DefaultPermissionGrantedCallback defaultGrantCallback, @NonNull Object externalLock) { @@ -146,6 +188,9 @@ public class PermissionManagerService { mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy( context, mHandlerThread.getLooper(), defaultGrantCallback, this); + SystemConfig systemConfig = SystemConfig.getInstance(); + mSystemPermissions = systemConfig.getSystemPermissions(); + mGlobalGids = systemConfig.getGlobalGids(); // propagate permission configuration final ArrayMap<String, SystemConfig.PermissionEntry> permConfig = @@ -230,14 +275,105 @@ public class PermissionManagerService { return PackageManager.PERMISSION_DENIED; } - private PermissionInfo getPermissionInfo(String name, String packageName, int flags, + private int checkUidPermission(String permName, int uid, int callingUid) { + final int callingUserId = UserHandle.getUserId(callingUid); + final boolean isCallerInstantApp = + mPackageManagerInt.getInstantAppPackageName(callingUid) != null; + final boolean isUidInstantApp = + mPackageManagerInt.getInstantAppPackageName(uid) != null; + final int userId = UserHandle.getUserId(uid); + if (!mUserManagerInt.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + + final String[] packages = mContext.getPackageManager().getPackagesForUid(uid); + if (packages != null && packages.length > 0) { + PackageParser.Package pkg = null; + for (String packageName : packages) { + pkg = mPackageManagerInt.getPackage(packageName); + if (pkg != null) { + break; + } + } + if (pkg == null) { +Slog.e(TAG, "TODD: No package not found; UID: " + uid); +Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages)); + return PackageManager.PERMISSION_DENIED; + } + if (pkg.mSharedUserId != null) { + if (isCallerInstantApp) { + return PackageManager.PERMISSION_DENIED; + } + } else { + if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) { + return PackageManager.PERMISSION_DENIED; + } + } + final PermissionsState permissionsState = + ((PackageSetting) pkg.mExtras).getPermissionsState(); + if (permissionsState.hasPermission(permName, userId)) { + if (isUidInstantApp) { + if (mSettings.isPermissionInstant(permName)) { + return PackageManager.PERMISSION_GRANTED; + } + } else { + return PackageManager.PERMISSION_GRANTED; + } + } + // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION + if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState + .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) { + return PackageManager.PERMISSION_GRANTED; + } + } else { + ArraySet<String> perms = mSystemPermissions.get(uid); + if (perms != null) { + if (perms.contains(permName)) { + return PackageManager.PERMISSION_GRANTED; + } + if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms + .contains(Manifest.permission.ACCESS_FINE_LOCATION)) { + return PackageManager.PERMISSION_GRANTED; + } + } + } + return PackageManager.PERMISSION_DENIED; + } + + private PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags, + int callingUid) { + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return null; + } + synchronized (mLock) { + return PackageParser.generatePermissionGroupInfo( + mSettings.mPermissionGroups.get(groupName), flags); + } + } + + private List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) { + if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { + return null; + } + synchronized (mLock) { + final int N = mSettings.mPermissionGroups.size(); + final ArrayList<PermissionGroupInfo> out + = new ArrayList<PermissionGroupInfo>(N); + for (PackageParser.PermissionGroup pg : mSettings.mPermissionGroups.values()) { + out.add(PackageParser.generatePermissionGroupInfo(pg, flags)); + } + return out; + } + } + + private PermissionInfo getPermissionInfo(String permName, String packageName, int flags, int callingUid) { if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { return null; } // reader synchronized (mLock) { - final BasePermission bp = mSettings.getPermissionLocked(name); + final BasePermission bp = mSettings.getPermissionLocked(permName); if (bp == null) { return null; } @@ -252,14 +388,10 @@ public class PermissionManagerService { if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) { return null; } - // reader synchronized (mLock) { - // TODO Uncomment when mPermissionGroups moves to this class -// if (groupName != null && !mPermissionGroups.containsKey(groupName)) { -// // This is thrown as NameNotFoundException -// return null; -// } - + if (groupName != null && !mSettings.mPermissionGroups.containsKey(groupName)) { + return null; + } final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10); for (BasePermission bp : mSettings.mPermissions.values()) { final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags); @@ -314,21 +446,21 @@ public class PermissionManagerService { // Assume by default that we did not install this permission into the system. p.info.flags &= ~PermissionInfo.FLAG_INSTALLED; - // Now that permission groups have a special meaning, we ignore permission - // groups for legacy apps to prevent unexpected behavior. In particular, - // permissions for one app being granted to someone just because they happen - // to be in a group defined by another app (before this had no implications). - if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { - p.group = mPackageManagerInt.getPermissionGroupTEMP(p.info.group); - // Warn for a permission in an unknown group. - if (PackageManagerService.DEBUG_PERMISSIONS - && p.info.group != null && p.group == null) { - Slog.i(TAG, "Permission " + p.info.name + " from package " - + p.info.packageName + " in an unknown group " + p.info.group); + synchronized (PermissionManagerService.this.mLock) { + // Now that permission groups have a special meaning, we ignore permission + // groups for legacy apps to prevent unexpected behavior. In particular, + // permissions for one app being granted to someone just because they happen + // to be in a group defined by another app (before this had no implications). + if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { + p.group = mSettings.mPermissionGroups.get(p.info.group); + // Warn for a permission in an unknown group. + if (DEBUG_PERMISSIONS + && p.info.group != null && p.group == null) { + Slog.i(TAG, "Permission " + p.info.name + " from package " + + p.info.packageName + " in an unknown group " + p.info.group); + } } - } - synchronized (PermissionManagerService.this.mLock) { if (p.tree) { final BasePermission bp = BasePermission.createOrUpdate( mSettings.getPermissionTreeLocked(p.info.name), p, pkg, @@ -344,6 +476,48 @@ public class PermissionManagerService { } } + private void addAllPermissionGroups(PackageParser.Package pkg, boolean chatty) { + final int N = pkg.permissionGroups.size(); + StringBuilder r = null; + for (int i=0; i<N; i++) { + final PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i); + final PackageParser.PermissionGroup cur = mSettings.mPermissionGroups.get(pg.info.name); + final String curPackageName = (cur == null) ? null : cur.info.packageName; + final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName); + if (cur == null || isPackageUpdate) { + mSettings.mPermissionGroups.put(pg.info.name, pg); + if (chatty && DEBUG_PACKAGE_SCANNING) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + if (isPackageUpdate) { + r.append("UPD:"); + } + r.append(pg.info.name); + } + } else { + Slog.w(TAG, "Permission group " + pg.info.name + " from package " + + pg.info.packageName + " ignored: original from " + + cur.info.packageName); + if (chatty && DEBUG_PACKAGE_SCANNING) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + r.append("DUP:"); + r.append(pg.info.name); + } + } + } + if (r != null && DEBUG_PACKAGE_SCANNING) { + Log.d(TAG, " Permission Groups: " + r); + } + + } + private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) { synchronized (mLock) { int N = pkg.permissions.size(); @@ -356,7 +530,7 @@ public class PermissionManagerService { } if (bp != null && bp.isPermission(p)) { bp.setPermission(null); - if (PackageManagerService.DEBUG_REMOVE && chatty) { + if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); } else { @@ -374,7 +548,7 @@ public class PermissionManagerService { } } if (r != null) { - if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); } N = pkg.requestedPermissions.size(); @@ -392,7 +566,7 @@ public class PermissionManagerService { } } if (r != null) { - if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); } } } @@ -455,6 +629,581 @@ public class PermissionManagerService { } } + private void grantPermissions(PackageParser.Package pkg, boolean replace, + String packageOfInterest, PermissionCallback callback) { + // IMPORTANT: There are two types of permissions: install and runtime. + // Install time permissions are granted when the app is installed to + // all device users and users added in the future. Runtime permissions + // are granted at runtime explicitly to specific users. Normal and signature + // protected permissions are install time permissions. Dangerous permissions + // are install permissions if the app's target SDK is Lollipop MR1 or older, + // otherwise they are runtime permissions. This function does not manage + // runtime permissions except for the case an app targeting Lollipop MR1 + // being upgraded to target a newer SDK, in which case dangerous permissions + // are transformed from install time to runtime ones. + + final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (ps == null) { + return; + } + final boolean isLegacySystemApp = mPackageManagerInt.isLegacySystemApp(pkg); + + final PermissionsState permissionsState = ps.getPermissionsState(); + PermissionsState origPermissions = permissionsState; + + final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); + + boolean runtimePermissionsRevoked = false; + int[] updatedUserIds = EMPTY_INT_ARRAY; + + boolean changedInstallPermission = false; + + if (replace) { + ps.setInstallPermissionsFixed(false); + if (!ps.isSharedUser()) { + origPermissions = new PermissionsState(permissionsState); + permissionsState.reset(); + } else { + // We need to know only about runtime permission changes since the + // calling code always writes the install permissions state but + // the runtime ones are written only if changed. The only cases of + // changed runtime permissions here are promotion of an install to + // runtime and revocation of a runtime from a shared user. + synchronized (mLock) { + updatedUserIds = revokeUnusedSharedUserPermissionsLocked( + ps.getSharedUser(), UserManagerService.getInstance().getUserIds()); + if (!ArrayUtils.isEmpty(updatedUserIds)) { + runtimePermissionsRevoked = true; + } + } + } + } + + permissionsState.setGlobalGids(mGlobalGids); + + synchronized (mLock) { + final int N = pkg.requestedPermissions.size(); + for (int i = 0; i < N; i++) { + final String permName = pkg.requestedPermissions.get(i); + final BasePermission bp = mSettings.getPermissionLocked(permName); + final boolean appSupportsRuntimePermissions = + pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; + + if (DEBUG_INSTALL) { + Log.i(TAG, "Package " + pkg.packageName + " checking " + permName + ": " + bp); + } + + if (bp == null || bp.getSourcePackageSetting() == null) { + if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Unknown permission " + permName + + " in package " + pkg.packageName); + } + } + continue; + } + + // Limit ephemeral apps to ephemeral allowed permissions. + if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) { + if (DEBUG_PERMISSIONS) { + Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + + " for package " + pkg.packageName); + } + continue; + } + + if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { + if (DEBUG_PERMISSIONS) { + Log.i(TAG, "Denying runtime-only permission " + bp.getName() + + " for package " + pkg.packageName); + } + continue; + } + + final String perm = bp.getName(); + boolean allowedSig = false; + int grant = GRANT_DENIED; + + // Keep track of app op permissions. + if (bp.isAppOp()) { + mSettings.addAppOpPackage(perm, pkg.packageName); + } + + if (bp.isNormal()) { + // For all apps normal permissions are install time ones. + grant = GRANT_INSTALL; + } else if (bp.isRuntime()) { + // If a permission review is required for legacy apps we represent + // their permissions as always granted runtime ones since we need + // to keep the review required permission flag per user while an + // install permission's state is shared across all users. + if (!appSupportsRuntimePermissions && !mSettings.mPermissionReviewRequired) { + // For legacy apps dangerous permissions are install time ones. + grant = GRANT_INSTALL; + } else if (origPermissions.hasInstallPermission(bp.getName())) { + // For legacy apps that became modern, install becomes runtime. + grant = GRANT_UPGRADE; + } else if (isLegacySystemApp) { + // For legacy system apps, install becomes runtime. + // We cannot check hasInstallPermission() for system apps since those + // permissions were granted implicitly and not persisted pre-M. + grant = GRANT_UPGRADE; + } else { + // For modern apps keep runtime permissions unchanged. + grant = GRANT_RUNTIME; + } + } else if (bp.isSignature()) { + // For all apps signature permissions are install time ones. + allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions); + if (allowedSig) { + grant = GRANT_INSTALL; + } + } + + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName); + } + + if (grant != GRANT_DENIED) { + if (!ps.isSystem() && ps.areInstallPermissionsFixed()) { + // If this is an existing, non-system package, then + // we can't add any new permissions to it. + if (!allowedSig && !origPermissions.hasInstallPermission(perm)) { + // Except... if this is a permission that was added + // to the platform (note: need to only do this when + // updating the platform). + if (!isNewPlatformPermissionForPackage(perm, pkg)) { + grant = GRANT_DENIED; + } + } + } + + switch (grant) { + case GRANT_INSTALL: { + // Revoke this as runtime permission to handle the case of + // a runtime permission being downgraded to an install one. + // Also in permission review mode we keep dangerous permissions + // for legacy apps + for (int userId : UserManagerService.getInstance().getUserIds()) { + if (origPermissions.getRuntimePermissionState( + perm, userId) != null) { + // Revoke the runtime permission and clear the flags. + origPermissions.revokeRuntimePermission(bp, userId); + origPermissions.updatePermissionFlags(bp, userId, + PackageManager.MASK_PERMISSION_FLAGS, 0); + // If we revoked a permission permission, we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + // Grant an install permission. + if (permissionsState.grantInstallPermission(bp) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + changedInstallPermission = true; + } + } break; + + case GRANT_RUNTIME: { + // Grant previously granted runtime permissions. + for (int userId : UserManagerService.getInstance().getUserIds()) { + final PermissionState permissionState = origPermissions + .getRuntimePermissionState(perm, userId); + int flags = permissionState != null + ? permissionState.getFlags() : 0; + if (origPermissions.hasRuntimePermission(perm, userId)) { + // Don't propagate the permission in a permission review + // mode if the former was revoked, i.e. marked to not + // propagate on upgrade. Note that in a permission review + // mode install permissions are represented as constantly + // granted runtime ones since we need to keep a per user + // state associated with the permission. Also the revoke + // on upgrade flag is no longer applicable and is reset. + final boolean revokeOnUpgrade = (flags & PackageManager + .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0; + if (revokeOnUpgrade) { + flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; + // Since we changed the flags, we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + if (!mSettings.mPermissionReviewRequired || !revokeOnUpgrade) { + if (permissionsState.grantRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // If we cannot put the permission as it was, + // we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + + // If the app supports runtime permissions no need for a review. + if (mSettings.mPermissionReviewRequired + && appSupportsRuntimePermissions + && (flags & PackageManager + .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; + // Since we changed the flags, we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } else if (mSettings.mPermissionReviewRequired + && !appSupportsRuntimePermissions) { + // For legacy apps that need a permission review, every new + // runtime permission is granted but it is pending a review. + // We also need to review only platform defined runtime + // permissions as these are the only ones the platform knows + // how to disable the API to simulate revocation as legacy + // apps don't expect to run with revoked permissions. + if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) { + if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { + flags |= FLAG_PERMISSION_REVIEW_REQUIRED; + // We changed the flags, hence have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + if (permissionsState.grantRuntimePermission(bp, userId) + != PermissionsState.PERMISSION_OPERATION_FAILURE) { + // We changed the permission, hence have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + // Propagate the permission flags. + permissionsState.updatePermissionFlags(bp, userId, flags, flags); + } + } break; + + case GRANT_UPGRADE: { + // Grant runtime permissions for a previously held install permission. + final PermissionState permissionState = origPermissions + .getInstallPermissionState(perm); + final int flags = + (permissionState != null) ? permissionState.getFlags() : 0; + + if (origPermissions.revokeInstallPermission(bp) + != PermissionsState.PERMISSION_OPERATION_FAILURE) { + // We will be transferring the permission flags, so clear them. + origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, 0); + changedInstallPermission = true; + } + + // If the permission is not to be promoted to runtime we ignore it and + // also its other flags as they are not applicable to install permissions. + if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) { + for (int userId : currentUserIds) { + if (permissionsState.grantRuntimePermission(bp, userId) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // Transfer the permission flags. + permissionsState.updatePermissionFlags(bp, userId, + flags, flags); + // If we granted the permission, we have to write. + updatedUserIds = ArrayUtils.appendInt( + updatedUserIds, userId); + } + } + } + } break; + + default: { + if (packageOfInterest == null + || packageOfInterest.equals(pkg.packageName)) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " because it was previously installed without"); + } + } + } break; + } + } else { + if (permissionsState.revokeInstallPermission(bp) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // Also drop the permission flags. + permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, 0); + changedInstallPermission = true; + Slog.i(TAG, "Un-granting permission " + perm + + " from package " + pkg.packageName + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) + + ")"); + } else if (bp.isAppOp()) { + // Don't print warning for app op permissions, since it is fine for them + // not to be granted, there is a UI for the user to decide. + if (DEBUG_PERMISSIONS + && (packageOfInterest == null + || packageOfInterest.equals(pkg.packageName))) { + Slog.i(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags) + + ")"); + } + } + } + } + + if ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() && + !ps.isSystem() || ps.isUpdatedSystem()) { + // This is the first that we have heard about this package, so the + // permissions we have now selected are fixed until explicitly + // changed. + ps.setInstallPermissionsFixed(true); + } + } + + // Persist the runtime permissions state for users with changes. If permissions + // were revoked because no app in the shared user declares them we have to + // write synchronously to avoid losing runtime permissions state. + if (callback != null) { + callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked); + } + } + + private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { + boolean allowed = false; + final int NP = PackageParser.NEW_PERMISSIONS.length; + for (int ip=0; ip<NP; ip++) { + final PackageParser.NewPermissionInfo npi + = PackageParser.NEW_PERMISSIONS[ip]; + if (npi.name.equals(perm) + && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) { + allowed = true; + Log.i(TAG, "Auto-granting " + perm + " to old pkg " + + pkg.packageName); + break; + } + } + return allowed; + } + + /** + * Determines whether a package is whitelisted for a particular privapp permission. + * + * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted. + * + * <p>This handles parent/child apps. + */ + private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) { + ArraySet<String> wlPermissions = SystemConfig.getInstance() + .getPrivAppPermissions(pkg.packageName); + // Let's check if this package is whitelisted... + boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm); + // If it's not, we'll also tail-recurse to the parent. + return whitelisted || + pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage); + } + + private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, + BasePermission bp, PermissionsState origPermissions) { + boolean oemPermission = bp.isOEM(); + boolean privilegedPermission = bp.isPrivileged(); + boolean privappPermissionsDisable = + RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE; + boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName()); + boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName); + if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged() + && !platformPackage && platformPermission) { + if (!hasPrivappWhitelistEntry(perm, pkg)) { + // Only report violations for apps on system image + if (!mSystemReady && !pkg.isUpdatedSystemApp()) { + // it's only a reportable violation if the permission isn't explicitly denied + final ArraySet<String> deniedPermissions = SystemConfig.getInstance() + .getPrivAppDenyPermissions(pkg.packageName); + final boolean permissionViolation = + deniedPermissions == null || !deniedPermissions.contains(perm); + if (permissionViolation) { + Slog.w(TAG, "Privileged permission " + perm + " for package " + + pkg.packageName + " - not in privapp-permissions whitelist"); + + if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { + if (mPrivappPermissionsViolations == null) { + mPrivappPermissionsViolations = new ArraySet<>(); + } + mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm); + } + } else { + return false; + } + } + if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { + return false; + } + } + } + final String systemPackageName = mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM); + final PackageParser.Package systemPackage = + mPackageManagerInt.getPackage(systemPackageName); + boolean allowed = (PackageManagerService.compareSignatures( + bp.getSourceSignatures(), pkg.mSignatures) + == PackageManager.SIGNATURE_MATCH) + || (PackageManagerService.compareSignatures( + systemPackage.mSignatures, pkg.mSignatures) + == PackageManager.SIGNATURE_MATCH); + if (!allowed && (privilegedPermission || oemPermission)) { + if (pkg.isSystem()) { + // For updated system applications, a privileged/oem permission + // is granted only if it had been defined by the original application. + if (pkg.isUpdatedSystemApp()) { + final PackageParser.Package disabledPkg = + mPackageManagerInt.getDisabledPackage(pkg.packageName); + final PackageSetting disabledPs = + (disabledPkg != null) ? (PackageSetting) disabledPkg.mExtras : null; + if (disabledPs != null + && disabledPs.getPermissionsState().hasInstallPermission(perm)) { + // If the original was granted this permission, we take + // that grant decision as read and propagate it to the + // update. + if ((privilegedPermission && disabledPs.isPrivileged()) + || (oemPermission && disabledPs.isOem() + && canGrantOemPermission(disabledPs, perm))) { + allowed = true; + } + } else { + // The system apk may have been updated with an older + // version of the one on the data partition, but which + // granted a new system permission that it didn't have + // before. In this case we do want to allow the app to + // now get the new permission if the ancestral apk is + // privileged to get it. + if (disabledPs != null && disabledPkg != null + && isPackageRequestingPermission(disabledPkg, perm) + && ((privilegedPermission && disabledPs.isPrivileged()) + || (oemPermission && disabledPs.isOem() + && canGrantOemPermission(disabledPs, perm)))) { + allowed = true; + } + // Also if a privileged parent package on the system image or any of + // its children requested a privileged/oem permission, the updated child + // packages can also get the permission. + if (pkg.parentPackage != null) { + final PackageParser.Package disabledParentPkg = mPackageManagerInt + .getDisabledPackage(pkg.parentPackage.packageName); + final PackageSetting disabledParentPs = (disabledParentPkg != null) + ? (PackageSetting) disabledParentPkg.mExtras : null; + if (disabledParentPkg != null + && ((privilegedPermission && disabledParentPs.isPrivileged()) + || (oemPermission && disabledParentPs.isOem()))) { + if (isPackageRequestingPermission(disabledParentPkg, perm) + && canGrantOemPermission(disabledParentPs, perm)) { + allowed = true; + } else if (disabledParentPkg.childPackages != null) { + for (PackageParser.Package disabledChildPkg + : disabledParentPkg.childPackages) { + final PackageSetting disabledChildPs = + (disabledChildPkg != null) + ? (PackageSetting) disabledChildPkg.mExtras + : null; + if (isPackageRequestingPermission(disabledChildPkg, perm) + && canGrantOemPermission( + disabledChildPs, perm)) { + allowed = true; + break; + } + } + } + } + } + } + } else { + final PackageSetting ps = (PackageSetting) pkg.mExtras; + allowed = (privilegedPermission && pkg.isPrivileged()) + || (oemPermission && pkg.isOem() + && canGrantOemPermission(ps, perm)); + } + } + } + if (!allowed) { + if (!allowed + && bp.isPre23() + && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { + // If this was a previously normal/dangerous permission that got moved + // to a system permission as part of the runtime permission redesign, then + // we still want to blindly grant it to old apps. + allowed = true; + } + if (!allowed && bp.isInstaller() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))) { + // If this permission is to be granted to the system installer and + // this app is an installer, then it gets the permission. + allowed = true; + } + if (!allowed && bp.isVerifier() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) { + // If this permission is to be granted to the system verifier and + // this app is a verifier, then it gets the permission. + allowed = true; + } + if (!allowed && bp.isPreInstalled() + && pkg.isSystem()) { + // Any pre-installed system app is allowed to get this permission. + allowed = true; + } + if (!allowed && bp.isDevelopment()) { + // For development permissions, a development permission + // is granted only if it was already granted. + allowed = origPermissions.hasInstallPermission(perm); + } + if (!allowed && bp.isSetup() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) { + // If this permission is to be granted to the system setup wizard and + // this app is a setup wizard, then it gets the permission. + allowed = true; + } + } + return allowed; + } + + private static boolean canGrantOemPermission(PackageSetting ps, String permission) { + if (!ps.isOem()) { + return false; + } + // all oem permissions must explicitly be granted or denied + final Boolean granted = + SystemConfig.getInstance().getOemPermissions(ps.name).get(permission); + if (granted == null) { + throw new IllegalStateException("OEM permission" + permission + " requested by package " + + ps.name + " must be explicitly declared granted or not"); + } + return Boolean.TRUE == granted; + } + + private boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId) { + if (!mSettings.mPermissionReviewRequired) { + return false; + } + + // Permission review applies only to apps not supporting the new permission model. + if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) { + return false; + } + + // Legacy apps have the permission and get user consent on launch. + if (pkg == null || pkg.mExtras == null) { + return false; + } + final PackageSetting ps = (PackageSetting) pkg.mExtras; + final PermissionsState permissionsState = ps.getPermissionsState(); + return permissionsState.isPermissionReviewRequired(userId); + } + + private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) { + final int permCount = pkg.requestedPermissions.size(); + for (int j = 0; j < permCount; j++) { + String requestedPermission = pkg.requestedPermissions.get(j); + if (permission.equals(requestedPermission)) { + return true; + } + } + return false; + } + private void grantRuntimePermissionsGrantedToDisabledPackageLocked( PackageParser.Package pkg, int callingUid, PermissionCallback callback) { if (pkg.parentPackage == null) { @@ -744,7 +1493,8 @@ public class PermissionManagerService { } } - private int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, int[] allUserIds) { + private int[] revokeUnusedSharedUserPermissionsLocked( + SharedUserSetting suSetting, int[] allUserIds) { // Collect all used permissions in the UID final ArraySet<String> usedPermissions = new ArraySet<>(); final List<PackageParser.Package> pkgList = suSetting.getPackages(); @@ -845,7 +1595,79 @@ public class PermissionManagerService { return permissionsState.getPermissionFlags(permName, userId); } - private int updatePermissions(String packageName, PackageParser.Package pkgInfo, int flags) { + private static final int UPDATE_PERMISSIONS_ALL = 1<<0; + private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1; + private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2; + + private void updatePermissions(String packageName, PackageParser.Package pkg, + boolean replaceGrant, Collection<PackageParser.Package> allPackages, + PermissionCallback callback) { + final int flags = (pkg != null ? UPDATE_PERMISSIONS_ALL : 0) | + (replaceGrant ? UPDATE_PERMISSIONS_REPLACE_PKG : 0); + updatePermissions( + packageName, pkg, getVolumeUuidForPackage(pkg), flags, allPackages, callback); + if (pkg != null && pkg.childPackages != null) { + for (PackageParser.Package childPkg : pkg.childPackages) { + updatePermissions(childPkg.packageName, childPkg, + getVolumeUuidForPackage(childPkg), flags, allPackages, callback); + } + } + } + + private void updateAllPermissions(String volumeUuid, boolean sdkUpdated, + Collection<PackageParser.Package> allPackages, PermissionCallback callback) { + final int flags = UPDATE_PERMISSIONS_ALL | + (sdkUpdated + ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL + : 0); + updatePermissions(null, null, volumeUuid, flags, allPackages, callback); + } + + private void updatePermissions(String changingPkgName, PackageParser.Package changingPkg, + String replaceVolumeUuid, int flags, Collection<PackageParser.Package> allPackages, + PermissionCallback callback) { + // TODO: Most of the methods exposing BasePermission internals [source package name, + // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't + // have package settings, we should make note of it elsewhere [map between + // source package name and BasePermission] and cycle through that here. Then we + // define a single method on BasePermission that takes a PackageSetting, changing + // package name and a package. + // NOTE: With this approach, we also don't need to tree trees differently than + // normal permissions. Today, we need two separate loops because these BasePermission + // objects are stored separately. + // Make sure there are no dangling permission trees. + flags = updatePermissionTrees(changingPkgName, changingPkg, flags); + + // Make sure all dynamic permissions have been assigned to a package, + // and make sure there are no dangling permissions. + flags = updatePermissions(changingPkgName, changingPkg, flags); + + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions"); + // Now update the permissions for all packages, in particular + // replace the granted permissions of the system packages. + if ((flags & UPDATE_PERMISSIONS_ALL) != 0) { + for (PackageParser.Package pkg : allPackages) { + if (pkg != changingPkg) { + // Only replace for packages on requested volume + final String volumeUuid = getVolumeUuidForPackage(pkg); + final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0) + && Objects.equals(replaceVolumeUuid, volumeUuid); + grantPermissions(pkg, replace, changingPkgName, callback); + } + } + } + + if (changingPkg != null) { + // Only replace for packages on requested volume + final String volumeUuid = getVolumeUuidForPackage(changingPkg); + final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0) + && Objects.equals(replaceVolumeUuid, volumeUuid); + grantPermissions(changingPkg, replace, changingPkgName, callback); + } + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + private int updatePermissions(String packageName, PackageParser.Package pkg, int flags) { Set<BasePermission> needsUpdate = null; synchronized (mLock) { final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator(); @@ -856,10 +1678,10 @@ public class PermissionManagerService { } if (bp.getSourcePackageSetting() != null) { if (packageName != null && packageName.equals(bp.getSourcePackageName()) - && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) { + && (pkg == null || !hasPermission(pkg, bp.getName()))) { Slog.i(TAG, "Removing old permission tree: " + bp.getName() + " from package " + bp.getSourcePackageName()); - flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL; + flags |= UPDATE_PERMISSIONS_ALL; it.remove(); } continue; @@ -872,13 +1694,13 @@ public class PermissionManagerService { } if (needsUpdate != null) { for (final BasePermission bp : needsUpdate) { - final PackageParser.Package pkg = + final PackageParser.Package sourcePkg = mPackageManagerInt.getPackage(bp.getSourcePackageName()); synchronized (mLock) { - if (pkg != null && pkg.mExtras != null) { - final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (sourcePkg != null && sourcePkg.mExtras != null) { + final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras; if (bp.getSourcePackageSetting() == null) { - bp.setSourcePackageSetting(ps); + bp.setSourcePackageSetting(sourcePs); } continue; } @@ -891,7 +1713,7 @@ public class PermissionManagerService { return flags; } - private int updatePermissionTrees(String packageName, PackageParser.Package pkgInfo, + private int updatePermissionTrees(String packageName, PackageParser.Package pkg, int flags) { Set<BasePermission> needsUpdate = null; synchronized (mLock) { @@ -900,10 +1722,10 @@ public class PermissionManagerService { final BasePermission bp = it.next(); if (bp.getSourcePackageSetting() != null) { if (packageName != null && packageName.equals(bp.getSourcePackageName()) - && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) { + && (pkg == null || !hasPermission(pkg, bp.getName()))) { Slog.i(TAG, "Removing old permission tree: " + bp.getName() + " from package " + bp.getSourcePackageName()); - flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL; + flags |= UPDATE_PERMISSIONS_ALL; it.remove(); } continue; @@ -916,13 +1738,13 @@ public class PermissionManagerService { } if (needsUpdate != null) { for (final BasePermission bp : needsUpdate) { - final PackageParser.Package pkg = + final PackageParser.Package sourcePkg = mPackageManagerInt.getPackage(bp.getSourcePackageName()); synchronized (mLock) { - if (pkg != null && pkg.mExtras != null) { - final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (sourcePkg != null && sourcePkg.mExtras != null) { + final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras; if (bp.getSourcePackageSetting() == null) { - bp.setSourcePackageSetting(ps); + bp.setSourcePackageSetting(sourcePs); } continue; } @@ -985,7 +1807,7 @@ public class PermissionManagerService { callback.onInstallPermissionUpdated(); } else if (permissionsState.getRuntimePermissionState(permName, userId) != null || hadState) { - callback.onPermissionUpdated(userId); + callback.onPermissionUpdated(new int[] { userId }, false); } } } @@ -1083,6 +1905,29 @@ public class PermissionManagerService { } } + private void systemReady() { + mSystemReady = true; + if (mPrivappPermissionsViolations != null) { + throw new IllegalStateException("Signature|privileged permissions not in " + + "privapp-permissions whitelist: " + mPrivappPermissionsViolations); + } + } + + private static String getVolumeUuidForPackage(PackageParser.Package pkg) { + if (pkg == null) { + return StorageManager.UUID_PRIVATE_INTERNAL; + } + if (pkg.isExternal()) { + if (TextUtils.isEmpty(pkg.volumeUuid)) { + return StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + return pkg.volumeUuid; + } + } else { + return StorageManager.UUID_PRIVATE_INTERNAL; + } + } + private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) { for (int i=pkgInfo.permissions.size()-1; i>=0; i--) { if (pkgInfo.permissions.get(i).info.name.equals(permName)) { @@ -1154,10 +1999,22 @@ public class PermissionManagerService { private class PermissionManagerInternalImpl extends PermissionManagerInternal { @Override + public void systemReady() { + PermissionManagerService.this.systemReady(); + } + @Override + public boolean isPermissionsReviewRequired(Package pkg, int userId) { + return PermissionManagerService.this.isPermissionsReviewRequired(pkg, userId); + } + @Override public void addAllPermissions(Package pkg, boolean chatty) { PermissionManagerService.this.addAllPermissions(pkg, chatty); } @Override + public void addAllPermissionGroups(Package pkg, boolean chatty) { + PermissionManagerService.this.addAllPermissionGroups(pkg, chatty); + } + @Override public void removeAllPermissions(Package pkg, boolean chatty) { PermissionManagerService.this.removeAllPermissions(pkg, chatty); } @@ -1198,10 +2055,16 @@ public class PermissionManagerService { overridePolicy, callingUid, userId, callback); } @Override - public int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, - int[] allUserIds) { - return PermissionManagerService.this.revokeUnusedSharedUserPermissions( - (SharedUserSetting) suSetting, allUserIds); + public void updatePermissions(String packageName, Package pkg, boolean replaceGrant, + Collection<PackageParser.Package> allPackages, PermissionCallback callback) { + PermissionManagerService.this.updatePermissions( + packageName, pkg, replaceGrant, allPackages, callback); + } + @Override + public void updateAllPermissions(String volumeUuid, boolean sdkUpdated, + Collection<PackageParser.Package> allPackages, PermissionCallback callback) { + PermissionManagerService.this.updateAllPermissions( + volumeUuid, sdkUpdated, allPackages, callback); } @Override public String[] getAppOpPermissionPackages(String permName) { @@ -1214,16 +2077,6 @@ public class PermissionManagerService { callingUid, userId); } @Override - public int updatePermissions(String packageName, - PackageParser.Package pkgInfo, int flags) { - return PermissionManagerService.this.updatePermissions(packageName, pkgInfo, flags); - } - @Override - public int updatePermissionTrees(String packageName, - PackageParser.Package pkgInfo, int flags) { - return PermissionManagerService.this.updatePermissionTrees(packageName, pkgInfo, flags); - } - @Override public void updatePermissionFlags(String permName, String packageName, int flagMask, int flagValues, int callingUid, int userId, PermissionCallback callback) { PermissionManagerService.this.updatePermissionFlags( @@ -1252,6 +2105,20 @@ public class PermissionManagerService { permName, packageName, callingUid, userId); } @Override + public int checkUidPermission(String permName, int uid, int callingUid) { + return PermissionManagerService.this.checkUidPermission(permName, uid, callingUid); + } + @Override + public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags, + int callingUid) { + return PermissionManagerService.this.getPermissionGroupInfo( + groupName, flags, callingUid); + } + @Override + public List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) { + return PermissionManagerService.this.getAllPermissionGroups(flags, callingUid); + } + @Override public PermissionInfo getPermissionInfo(String permName, String packageName, int flags, int callingUid) { return PermissionManagerService.this.getPermissionInfo( @@ -1276,17 +2143,5 @@ public class PermissionManagerService { return mSettings.getPermissionLocked(permName); } } - @Override - public void putPermissionTEMP(String permName, BasePermission permission) { - synchronized (PermissionManagerService.this.mLock) { - mSettings.putPermissionLocked(permName, (BasePermission) permission); - } - } - @Override - public Iterator<BasePermission> getPermissionIteratorTEMP() { - synchronized (PermissionManagerService.this.mLock) { - return mSettings.getAllPermissionsLocked().iterator(); - } - } } } diff --git a/com/android/server/pm/permission/PermissionSettings.java b/com/android/server/pm/permission/PermissionSettings.java index 7d125c9e..f6c4990c 100644 --- a/com/android/server/pm/permission/PermissionSettings.java +++ b/com/android/server/pm/permission/PermissionSettings.java @@ -19,6 +19,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.PackageParser; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -46,7 +47,8 @@ import java.util.Set; */ public class PermissionSettings { - final boolean mPermissionReviewRequired; + public final boolean mPermissionReviewRequired; + /** * All of the permissions known to the system. The mapping is from permission * name to permission object. @@ -64,6 +66,14 @@ public class PermissionSettings { new ArrayMap<String, BasePermission>(); /** + * All permisson groups know to the system. The mapping is from permission group + * name to permission group object. + */ + @GuardedBy("mLock") + final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups = + new ArrayMap<String, PackageParser.PermissionGroup>(); + + /** * Set of packages that request a particular app op. The mapping is from permission * name to package names. */ diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java index b917dae2..2494bded 100644 --- a/com/android/server/power/PowerManagerService.java +++ b/com/android/server/power/PowerManagerService.java @@ -58,10 +58,6 @@ import android.os.WorkSource; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; -import android.service.power.PowerServiceDumpProto; -import android.service.power.PowerServiceSettingsAndConfigurationDumpProto; -import android.service.power.SuspendBlockerProto; -import android.service.power.WakeLockProto; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; import android.util.EventLog; @@ -620,8 +616,8 @@ public final class PowerManagerService extends SystemService } void dumpProto(ProtoOutputStream proto) { - final long constantsToken = proto.start(PowerServiceDumpProto.CONSTANTS); - proto.write(PowerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS, + final long constantsToken = proto.start(PowerManagerServiceDumpProto.CONSTANTS); + proto.write(PowerManagerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS, NO_CACHED_WAKE_LOCKS); proto.end(constantsToken); } @@ -3396,112 +3392,112 @@ public final class PowerManagerService extends SystemService synchronized (mLock) { mConstants.dumpProto(proto); - proto.write(PowerServiceDumpProto.DIRTY, mDirty); - proto.write(PowerServiceDumpProto.WAKEFULNESS, mWakefulness); - proto.write(PowerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging); - proto.write(PowerServiceDumpProto.IS_POWERED, mIsPowered); - proto.write(PowerServiceDumpProto.PLUG_TYPE, mPlugType); - proto.write(PowerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel); + proto.write(PowerManagerServiceDumpProto.DIRTY, mDirty); + proto.write(PowerManagerServiceDumpProto.WAKEFULNESS, mWakefulness); + proto.write(PowerManagerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging); + proto.write(PowerManagerServiceDumpProto.IS_POWERED, mIsPowered); + proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType); + proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel); proto.write( - PowerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED, + PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED, mBatteryLevelWhenDreamStarted); - proto.write(PowerServiceDumpProto.DOCK_STATE, mDockState); - proto.write(PowerServiceDumpProto.IS_STAY_ON, mStayOn); - proto.write(PowerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive); - proto.write(PowerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted); - proto.write(PowerServiceDumpProto.IS_SYSTEM_READY, mSystemReady); + proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState); + proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn); + proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive); + proto.write(PowerManagerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted); + proto.write(PowerManagerServiceDumpProto.IS_SYSTEM_READY, mSystemReady); proto.write( - PowerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED, + PowerManagerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED, mHalAutoSuspendModeEnabled); proto.write( - PowerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED, + PowerManagerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED, mHalInteractiveModeEnabled); - final long activeWakeLocksToken = proto.start(PowerServiceDumpProto.ACTIVE_WAKE_LOCKS); + final long activeWakeLocksToken = proto.start(PowerManagerServiceDumpProto.ACTIVE_WAKE_LOCKS); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_CPU, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_CPU, (mWakeLockSummary & WAKE_LOCK_CPU) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT, (mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM, (mWakeLockSummary & WAKE_LOCK_SCREEN_DIM) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT, (mWakeLockSummary & WAKE_LOCK_BUTTON_BRIGHT) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF, (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE, (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE, (mWakeLockSummary & WAKE_LOCK_DOZE) != 0); proto.write( - PowerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW, + PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW, (mWakeLockSummary & WAKE_LOCK_DRAW) != 0); proto.end(activeWakeLocksToken); - proto.write(PowerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled); - proto.write(PowerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched); - proto.write(PowerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck); + proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled); + proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched); + proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck); - final long userActivityToken = proto.start(PowerServiceDumpProto.USER_ACTIVITY); + final long userActivityToken = proto.start(PowerManagerServiceDumpProto.USER_ACTIVITY); proto.write( - PowerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT, + PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT, (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0); proto.write( - PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM, + PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM, (mUserActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0); proto.write( - PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM, + PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM, (mUserActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0); proto.end(userActivityToken); proto.write( - PowerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY, + PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY, mRequestWaitForNegativeProximity); - proto.write(PowerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled); - proto.write(PowerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned); - proto.write(PowerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled); - proto.write(PowerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow); - proto.write(PowerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode); - proto.write(PowerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode); + proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled); + proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned); + proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled); + proto.write(PowerManagerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow); + proto.write(PowerManagerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode); + proto.write(PowerManagerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode); for (int id : mDeviceIdleWhitelist) { - proto.write(PowerServiceDumpProto.DEVICE_IDLE_WHITELIST, id); + proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_WHITELIST, id); } for (int id : mDeviceIdleTempWhitelist) { - proto.write(PowerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id); + proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id); } - proto.write(PowerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime); - proto.write(PowerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime); - proto.write(PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime); + proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime); + proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime); + proto.write(PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime); proto.write( - PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS, + PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS, mLastUserActivityTimeNoChangeLights); proto.write( - PowerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS, + PowerManagerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS, mLastInteractivePowerHintTime); proto.write( - PowerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS, + PowerManagerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS, mLastScreenBrightnessBoostTime); proto.write( - PowerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS, + PowerManagerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS, mScreenBrightnessBoostInProgress); - proto.write(PowerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady); + proto.write(PowerManagerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady); proto.write( - PowerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER, + PowerManagerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER, mHoldingWakeLockSuspendBlocker); proto.write( - PowerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER, + PowerManagerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER, mHoldingDisplaySuspendBlocker); final long settingsAndConfigurationToken = - proto.start(PowerServiceDumpProto.SETTINGS_AND_CONFIGURATION); + proto.start(PowerManagerServiceDumpProto.SETTINGS_AND_CONFIGURATION); proto.write( PowerServiceSettingsAndConfigurationDumpProto .IS_DECOUPLE_HAL_AUTO_SUSPEND_MODE_FROM_DISPLAY_CONFIG, @@ -3698,42 +3694,43 @@ public final class PowerManagerService extends SystemService final int sleepTimeout = getSleepTimeoutLocked(); final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout); final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); - proto.write(PowerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout); - proto.write(PowerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout); - proto.write(PowerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration); - proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging); - proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged); + proto.write(PowerManagerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout); + proto.write(PowerManagerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout); + proto.write(PowerManagerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration); + proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging); + proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged); for (int i = 0; i < mUidState.size(); i++) { final UidState state = mUidState.valueAt(i); - final long uIDToken = proto.start(PowerServiceDumpProto.UIDS); + final long uIDToken = proto.start(PowerManagerServiceDumpProto.UID_STATES); final int uid = mUidState.keyAt(i); - proto.write(PowerServiceDumpProto.UidProto.UID, uid); - proto.write(PowerServiceDumpProto.UidProto.UID_STRING, UserHandle.formatUid(uid)); - proto.write(PowerServiceDumpProto.UidProto.IS_ACTIVE, state.mActive); - proto.write(PowerServiceDumpProto.UidProto.NUM_WAKE_LOCKS, state.mNumWakeLocks); + proto.write(PowerManagerServiceDumpProto.UidStateProto.UID, uid); + proto.write(PowerManagerServiceDumpProto.UidStateProto.UID_STRING, UserHandle.formatUid(uid)); + proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_ACTIVE, state.mActive); + proto.write(PowerManagerServiceDumpProto.UidStateProto.NUM_WAKE_LOCKS, state.mNumWakeLocks); if (state.mProcState == ActivityManager.PROCESS_STATE_UNKNOWN) { - proto.write(PowerServiceDumpProto.UidProto.IS_PROCESS_STATE_UNKNOWN, true); + proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_PROCESS_STATE_UNKNOWN, true); } else { - proto.write(PowerServiceDumpProto.UidProto.PROCESS_STATE, state.mProcState); + proto.write(PowerManagerServiceDumpProto.UidStateProto.PROCESS_STATE, + ActivityManager.processStateAmToProto(state.mProcState)); } proto.end(uIDToken); } - mHandler.getLooper().writeToProto(proto, PowerServiceDumpProto.LOOPER); + mHandler.getLooper().writeToProto(proto, PowerManagerServiceDumpProto.LOOPER); for (WakeLock wl : mWakeLocks) { - wl.writeToProto(proto, PowerServiceDumpProto.WAKE_LOCKS); + wl.writeToProto(proto, PowerManagerServiceDumpProto.WAKE_LOCKS); } for (SuspendBlocker sb : mSuspendBlockers) { - sb.writeToProto(proto, PowerServiceDumpProto.SUSPEND_BLOCKERS); + sb.writeToProto(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS); } wcd = mWirelessChargerDetector; } if (wcd != null) { - wcd.writeToProto(proto, PowerServiceDumpProto.WIRELESS_CHARGER_DETECTOR); + wcd.writeToProto(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR); } proto.flush(); } diff --git a/com/android/server/power/WirelessChargerDetector.java b/com/android/server/power/WirelessChargerDetector.java index 6ee9dcd3..54487e39 100644 --- a/com/android/server/power/WirelessChargerDetector.java +++ b/com/android/server/power/WirelessChargerDetector.java @@ -24,7 +24,6 @@ import android.os.BatteryManager; import android.os.Handler; import android.os.Message; import android.os.SystemClock; -import android.service.power.WirelessChargerDetectorProto; import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; diff --git a/com/android/server/usage/AppIdleHistory.java b/com/android/server/usage/AppIdleHistory.java index f2985597..e5d3915d 100644 --- a/com/android/server/usage/AppIdleHistory.java +++ b/com/android/server/usage/AppIdleHistory.java @@ -16,6 +16,9 @@ package com.android.server.usage; +import static android.app.usage.AppStandby.*; + +import android.app.usage.AppStandby; import android.os.Environment; import android.os.SystemClock; import android.util.ArrayMap; @@ -51,8 +54,10 @@ public class AppIdleHistory { private static final String TAG = "AppIdleHistory"; + private static final boolean DEBUG = AppStandbyController.DEBUG; + // History for all users and all packages - private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>(); + private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>(); private long mLastPeriod = 0; private static final long ONE_MINUTE = 60 * 1000; private static final int HISTORY_SIZE = 100; @@ -70,6 +75,13 @@ public class AppIdleHistory { private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; // Elapsed timebase time when app was last used private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; + private static final String ATTR_CURRENT_BUCKET = "appLimitBucket"; + private static final String ATTR_BUCKETING_REASON = "bucketReason"; + + // State that was last informed to listeners, since boot + private static final int STATE_UNINFORMED = 0; + private static final int STATE_ACTIVE = 1; + private static final int STATE_IDLE = 2; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration @@ -85,17 +97,15 @@ public class AppIdleHistory { private boolean mScreenOn; - private static class PackageHistory { + private static class AppUsageHistory { final byte[] recent = new byte[HISTORY_SIZE]; long lastUsedElapsedTime; long lastUsedScreenTime; + @StandbyBuckets int currentBucket; + String bucketingReason; + int lastInformedState; } - AppIdleHistory(long elapsedRealtime) { - this(Environment.getDataSystemDirectory(), elapsedRealtime); - } - - @VisibleForTesting AppIdleHistory(File storageDir, long elapsedRealtime) { mElapsedSnapshot = elapsedRealtime; mScreenOnSnapshot = elapsedRealtime; @@ -119,6 +129,9 @@ public class AppIdleHistory { mElapsedDuration += elapsedRealtime - mElapsedSnapshot; mElapsedSnapshot = elapsedRealtime; } + if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot + + ", mScreenOnDuration=" + mScreenOnDuration + + ", mScreenOn=" + mScreenOn); } public long getScreenOnTime(long elapsedRealtime) { @@ -174,29 +187,35 @@ public class AppIdleHistory { } public void reportUsage(String packageName, int userId, long elapsedRealtime) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); - PackageHistory packageHistory = getPackageHistory(userHistory, packageName, - elapsedRealtime); + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); shiftHistoryToNow(userHistory, elapsedRealtime); - packageHistory.lastUsedElapsedTime = mElapsedDuration + appUsageHistory.lastUsedElapsedTime = mElapsedDuration + (elapsedRealtime - mElapsedSnapshot); - packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); - packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE; + 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); + } } public void setIdle(String packageName, int userId, long elapsedRealtime) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); - PackageHistory packageHistory = getPackageHistory(userHistory, packageName, - elapsedRealtime); + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); shiftHistoryToNow(userHistory, elapsedRealtime); - packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE; + appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE; } - private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory, + private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory, long elapsedRealtime) { long thisPeriod = elapsedRealtime / PERIOD_DURATION; // Has the period switched over? Slide all users' package histories @@ -206,7 +225,7 @@ public class AppIdleHistory { final int NUSERS = mIdleHistory.size(); for (int u = 0; u < NUSERS; u++) { userHistory = mIdleHistory.valueAt(u); - for (PackageHistory idleState : userHistory.values()) { + for (AppUsageHistory idleState : userHistory.values()) { // Shift left System.arraycopy(idleState.recent, diff, idleState.recent, 0, HISTORY_SIZE - diff); @@ -221,8 +240,8 @@ public class AppIdleHistory { mLastPeriod = thisPeriod; } - private ArrayMap<String, PackageHistory> getUserHistory(int userId) { - ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); + private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) { + ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); if (userHistory == null) { userHistory = new ArrayMap<>(); mIdleHistory.put(userId, userHistory); @@ -231,16 +250,18 @@ public class AppIdleHistory { return userHistory; } - private PackageHistory getPackageHistory(ArrayMap<String, PackageHistory> userHistory, - String packageName, long elapsedRealtime) { - PackageHistory packageHistory = userHistory.get(packageName); - if (packageHistory == null) { - packageHistory = new PackageHistory(); - packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime); - packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); - userHistory.put(packageName, packageHistory); + private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory, + String packageName, long elapsedRealtime, boolean create) { + AppUsageHistory appUsageHistory = userHistory.get(packageName); + if (appUsageHistory == null && create) { + appUsageHistory = new AppUsageHistory(); + appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime); + appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); + appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_NEVER; + appUsageHistory.bucketingReason = REASON_DEFAULT; + userHistory.put(packageName, appUsageHistory); } - return packageHistory; + return appUsageHistory; } public void onUserRemoved(int userId) { @@ -248,48 +269,124 @@ public class AppIdleHistory { } public boolean isIdle(String packageName, int userId, long elapsedRealtime) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); - PackageHistory packageHistory = - getPackageHistory(userHistory, packageName, elapsedRealtime); - if (packageHistory == null) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, true); + if (appUsageHistory == null) { return false; // Default to not idle } else { - return hasPassedThresholds(packageHistory, elapsedRealtime); + return appUsageHistory.currentBucket >= AppStandby.STANDBY_BUCKET_RARE; + // Whether or not it's passed will now be externally calculated and the + // bucket will be pushed to the history using setAppStandbyBucket() + //return hasPassedThresholds(appUsageHistory, elapsedRealtime); } } + public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, + int bucket, String reason) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, true); + appUsageHistory.currentBucket = bucket; + appUsageHistory.bucketingReason = reason; + if (DEBUG) { + Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket + + ", reason=" + appUsageHistory.bucketingReason); + } + } + + public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, true); + return appUsageHistory.currentBucket; + } + + public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = + getPackageHistory(userHistory, packageName, elapsedRealtime, false); + return appUsageHistory != null ? appUsageHistory.bucketingReason : null; + } + private long getElapsedTime(long elapsedRealtime) { return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration); } public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); - PackageHistory packageHistory = getPackageHistory(userHistory, packageName, - elapsedRealtime); - packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime) - - mElapsedTimeThreshold; - packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime) - - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */; + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); + if (idle) { + appUsageHistory.currentBucket = STANDBY_BUCKET_RARE; + appUsageHistory.bucketingReason = REASON_FORCED; + } else { + appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE; + // This is to pretend that the app was just used, don't freeze the state anymore. + appUsageHistory.bucketingReason = REASON_USAGE; + } } public void clearUsage(String packageName, int userId) { - ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); userHistory.remove(packageName); } - private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) { - return (packageHistory.lastUsedScreenTime - <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold) - && (packageHistory.lastUsedElapsedTime - <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold); + boolean shouldInformListeners(String packageName, int userId, + long elapsedRealtime, boolean isIdle) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, true); + int targetState = isIdle? STATE_IDLE : STATE_ACTIVE; + if (appUsageHistory.lastInformedState != (isIdle ? STATE_IDLE : STATE_ACTIVE)) { + appUsageHistory.lastInformedState = targetState; + return true; + } + return false; + } + + /** + * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds + * that corresponds to how long since the app was used. + * @param packageName + * @param userId + * @param elapsedRealtime current time + * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0 + * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0 + * @return The index whose values the app's used time exceeds (in both arrays) + */ + int getThresholdIndex(String packageName, int userId, long elapsedRealtime, + long[] screenTimeThresholds, long[] elapsedTimeThresholds) { + ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); + AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, + elapsedRealtime, false); + // If we don't have any state for the app, assume never used + if (appUsageHistory == null) return screenTimeThresholds.length - 1; + + long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime; + long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime; + + if (DEBUG) Slog.d(TAG, packageName + + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime + + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime); + if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta + + ", elapsed=" + elapsedDelta); + for (int i = screenTimeThresholds.length - 1; i >= 0; i--) { + if (screenOnDelta >= screenTimeThresholds[i] + && elapsedDelta >= elapsedTimeThresholds[i]) { + return i; + } + } + return 0; } - private File getUserFile(int userId) { + @VisibleForTesting + File getUserFile(int userId) { return new File(new File(new File(mStorageDir, "users"), Integer.toString(userId)), APP_IDLE_FILENAME); } - private void readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory) { + private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) { FileInputStream fis = null; try { AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); @@ -315,12 +412,22 @@ public class AppIdleHistory { final String name = parser.getName(); if (name.equals(TAG_PACKAGE)) { final String packageName = parser.getAttributeValue(null, ATTR_NAME); - PackageHistory packageHistory = new PackageHistory(); - packageHistory.lastUsedElapsedTime = + AppUsageHistory appUsageHistory = new AppUsageHistory(); + appUsageHistory.lastUsedElapsedTime = Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); - packageHistory.lastUsedScreenTime = + appUsageHistory.lastUsedScreenTime = Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); - userHistory.put(packageName, packageHistory); + String currentBucketString = parser.getAttributeValue(null, + ATTR_CURRENT_BUCKET); + appUsageHistory.currentBucket = currentBucketString == null + ? AppStandby.STANDBY_BUCKET_ACTIVE + : Integer.parseInt(currentBucketString); + appUsageHistory.bucketingReason = + parser.getAttributeValue(null, ATTR_BUCKETING_REASON); + if (appUsageHistory.bucketingReason == null) { + appUsageHistory.bucketingReason = REASON_DEFAULT; + } + userHistory.put(packageName, appUsageHistory); } } } @@ -345,17 +452,20 @@ public class AppIdleHistory { xml.startTag(null, TAG_PACKAGES); - ArrayMap<String,PackageHistory> userHistory = getUserHistory(userId); + ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId); final int N = userHistory.size(); for (int i = 0; i < N; i++) { String packageName = userHistory.keyAt(i); - PackageHistory history = userHistory.valueAt(i); + AppUsageHistory history = userHistory.valueAt(i); xml.startTag(null, TAG_PACKAGE); xml.attribute(null, ATTR_NAME, packageName); xml.attribute(null, ATTR_ELAPSED_IDLE, Long.toString(history.lastUsedElapsedTime)); xml.attribute(null, ATTR_SCREEN_IDLE, Long.toString(history.lastUsedScreenTime)); + xml.attribute(null, ATTR_CURRENT_BUCKET, + Integer.toString(history.currentBucket)); + xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason); xml.endTag(null, TAG_PACKAGE); } @@ -368,10 +478,10 @@ public class AppIdleHistory { } } - public void dump(IndentingPrintWriter idpw, int userId) { + public void dump(IndentingPrintWriter idpw, int userId, String pkg) { idpw.println("Package idle stats:"); idpw.increaseIndent(); - ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); + ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); final long elapsedRealtime = SystemClock.elapsedRealtime(); final long totalElapsedTime = getElapsedTime(elapsedRealtime); final long screenOnTime = getScreenOnTime(elapsedRealtime); @@ -379,13 +489,18 @@ public class AppIdleHistory { final int P = userHistory.size(); for (int p = 0; p < P; p++) { final String packageName = userHistory.keyAt(p); - final PackageHistory packageHistory = userHistory.valueAt(p); + final AppUsageHistory appUsageHistory = userHistory.valueAt(p); + if (pkg != null && !pkg.equals(packageName)) { + continue; + } idpw.print("package=" + packageName); idpw.print(" lastUsedElapsed="); - TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw); + TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); idpw.print(" lastUsedScreenOn="); - TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw); + TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); + idpw.print(" bucket=" + appUsageHistory.currentBucket + + " reason=" + appUsageHistory.bucketingReason); idpw.println(); } idpw.println(); @@ -399,7 +514,7 @@ public class AppIdleHistory { } public void dumpHistory(IndentingPrintWriter idpw, int userId) { - ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); + ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId); final long elapsedRealtime = SystemClock.elapsedRealtime(); if (userHistory == null) return; final int P = userHistory.size(); diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java index b2446ba7..17fde579 100644 --- a/com/android/server/usage/AppStandbyController.java +++ b/com/android/server/usage/AppStandbyController.java @@ -18,13 +18,14 @@ package com.android.server.usage; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; -import static com.android.server.usage.UsageStatsService.MSG_REPORT_EVENT; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.admin.DevicePolicyManager; +import android.app.usage.AppStandby; +import android.app.usage.AppStandby.StandbyBuckets; import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManagerInternal; +import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; import android.appwidget.AppWidgetManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -41,6 +42,7 @@ import android.hardware.display.DisplayManager; import android.net.NetworkScoreManager; import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.Environment; import android.os.Handler; import android.os.IDeviceIdleController; import android.os.Looper; @@ -66,8 +68,10 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; +import java.io.File; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -76,10 +80,33 @@ import java.util.List; public class AppStandbyController { private static final String TAG = "AppStandbyController"; - private static final boolean DEBUG = false; + static final boolean DEBUG = false; static final boolean COMPRESS_TIME = false; private static final long ONE_MINUTE = 60 * 1000; + private static final long ONE_HOUR = ONE_MINUTE * 60; + private static final long ONE_DAY = ONE_HOUR * 24; + + static final long[] SCREEN_TIME_THRESHOLDS = { + 0, + 0, + COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR, + COMPRESS_TIME ? 240 * 1000 : 8 * 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 + }; + + static final int[] THRESHOLD_BUCKETS = { + AppStandby.STANDBY_BUCKET_ACTIVE, + AppStandby.STANDBY_BUCKET_WORKING_SET, + AppStandby.STANDBY_BUCKET_FREQUENT, + AppStandby.STANDBY_BUCKET_RARE + }; // To name the lock for stack traces static class Lock {} @@ -92,7 +119,7 @@ public class AppStandbyController { private AppIdleHistory mAppIdleHistory; @GuardedBy("mAppIdleLock") - private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener> + private ArrayList<AppIdleStateChangeListener> mPackageAccessListeners = new ArrayList<>(); /** Whether we've queried the list of carrier privileged apps. */ @@ -118,6 +145,9 @@ public class AppStandbyController { long mAppIdleWallclockThresholdMillis; long mAppIdleParoleIntervalMillis; long mAppIdleParoleDurationMillis; + long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS; + long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS; + boolean mAppIdleEnabled; boolean mAppIdleTempParoled; boolean mCharging; @@ -129,20 +159,26 @@ public class AppStandbyController { private final Handler mHandler; private final Context mContext; - private DisplayManager mDisplayManager; - private IDeviceIdleController mDeviceIdleController; + // TODO: Provide a mechanism to set an external bucketing service + private boolean mUseInternalBucketingHeuristics = true; + private AppWidgetManager mAppWidgetManager; - private IBatteryStats mBatteryStats; private PowerManager mPowerManager; private PackageManager mPackageManager; - private PackageManagerInternal mPackageManagerInternal; + private Injector mInjector; + AppStandbyController(Context context, Looper looper) { - mContext = context; - mHandler = new AppStandbyHandler(looper); + this(new Injector(context, looper)); + } + + AppStandbyController(Injector injector) { + mInjector = injector; + mContext = mInjector.getContext(); + mHandler = new AppStandbyHandler(mInjector.getLooper()); mPackageManager = mContext.getPackageManager(); - mAppIdleEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enableAutoPowerModes); + mAppIdleEnabled = mInjector.isAppIdleEnabled(); + if (mAppIdleEnabled) { IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); deviceStates.addAction(BatteryManager.ACTION_DISCHARGING); @@ -150,7 +186,8 @@ public class AppStandbyController { mContext.registerReceiver(new DeviceStateReceiver(), deviceStates); } synchronized (mAppIdleLock) { - mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime()); + mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(), + mInjector.elapsedRealtime()); } IntentFilter packageFilter = new IntentFilter(); @@ -164,6 +201,7 @@ public class AppStandbyController { } public void onBootPhase(int phase) { + mInjector.onBootPhase(phase); if (phase == PHASE_SYSTEM_SERVICES_READY) { // Observe changes to the threshold SettingsObserver settingsObserver = new SettingsObserver(mHandler); @@ -171,18 +209,11 @@ public class AppStandbyController { settingsObserver.updateSettings(); mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class); - mDeviceIdleController = IDeviceIdleController.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); - mBatteryStats = IBatteryStats.Stub.asInterface( - ServiceManager.getService(BatteryStats.SERVICE_NAME)); - mDisplayManager = (DisplayManager) mContext.getSystemService( - Context.DISPLAY_SERVICE); mPowerManager = mContext.getSystemService(PowerManager.class); - mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); + mInjector.registerDisplayListener(mDisplayListener, mHandler); synchronized (mAppIdleLock) { - mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime()); + mAppIdleHistory.updateDisplay(isDisplayOn(), mInjector.elapsedRealtime()); } if (mPendingOneTimeCheckIdleStates) { @@ -191,7 +222,7 @@ public class AppStandbyController { mSystemServicesReady = true; } else if (phase == PHASE_BOOT_COMPLETED) { - setChargingState(mContext.getSystemService(BatteryManager.class).isCharging()); + setChargingState(mInjector.isCharging()); } } @@ -229,7 +260,7 @@ public class AppStandbyController { /** Paroled here means temporary pardon from being inactive */ void setAppIdleParoled(boolean paroled) { synchronized (mAppIdleLock) { - final long now = System.currentTimeMillis(); + final long now = mInjector.currentTimeMillis(); if (mAppIdleTempParoled != paroled) { mAppIdleTempParoled = paroled; if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled); @@ -284,7 +315,7 @@ public class AppStandbyController { * scheduling a series of repeating checkIdleStates each time we fired off one. */ void postOneTimeCheckIdleStates() { - if (mDeviceIdleController == null) { + if (mInjector.getBootPhase() < PHASE_SYSTEM_SERVICES_READY) { // Not booted yet; wait for it! mPendingOneTimeCheckIdleStates = true; } else { @@ -304,7 +335,7 @@ public class AppStandbyController { final int[] runningUserIds; try { - runningUserIds = ActivityManager.getService().getRunningUserIds(); + runningUserIds = mInjector.getRunningUserIds(); if (checkUserId != UserHandle.USER_ALL && !ArrayUtils.contains(runningUserIds, checkUserId)) { return false; @@ -313,7 +344,7 @@ public class AppStandbyController { throw re.rethrowFromSystemServer(); } - final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long elapsedRealtime = mInjector.elapsedRealtime(); for (int i = 0; i < runningUserIds.length; i++) { final int userId = runningUserIds[i]; if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) { @@ -329,30 +360,71 @@ public class AppStandbyController { for (int p = 0; p < packageCount; p++) { final PackageInfo pi = packages.get(p); final String packageName = pi.packageName; - final boolean isIdle = isAppIdleFiltered(packageName, + final boolean isSpecial = isAppSpecial(packageName, UserHandle.getAppId(pi.applicationInfo.uid), - userId, elapsedRealtime); - mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, - userId, isIdle ? 1 : 0, packageName)); - if (isIdle) { + userId); + if (DEBUG) { + Slog.d(TAG, " Checking idle state for " + packageName); + } + if (isSpecial) { + maybeInformListeners(packageName, userId, elapsedRealtime, false); + } else if (mUseInternalBucketingHeuristics) { synchronized (mAppIdleLock) { - mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime); + int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId, + elapsedRealtime); + String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName, + userId, elapsedRealtime); + if (bucketingReason != null + && (bucketingReason.equals(AppStandby.REASON_FORCED) + || bucketingReason.startsWith(AppStandby.REASON_PREDICTED))) { + continue; + } + int newBucket = getBucketForLocked(packageName, userId, + elapsedRealtime); + if (DEBUG) { + Slog.d(TAG, " Old bucket=" + oldBucket + + ", newBucket=" + newBucket); + } + if (oldBucket != newBucket) { + mAppIdleHistory.setAppStandbyBucket(packageName, userId, + elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT); + maybeInformListeners(packageName, userId, elapsedRealtime, + newBucket >= AppStandby.STANDBY_BUCKET_RARE); + } } } } } if (DEBUG) { Slog.d(TAG, "checkIdleStates took " - + (SystemClock.elapsedRealtime() - elapsedRealtime)); + + (mInjector.elapsedRealtime() - elapsedRealtime)); } return true; } + private void maybeInformListeners(String packageName, int userId, + long elapsedRealtime, boolean isIdle) { + synchronized (mAppIdleLock) { + if (mAppIdleHistory.shouldInformListeners(packageName, userId, + elapsedRealtime, isIdle)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, + userId, isIdle ? 1 : 0, packageName)); + } + } + } + + @StandbyBuckets int getBucketForLocked(String packageName, int userId, + long elapsedRealtime) { + int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId, + elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds); + return THRESHOLD_BUCKETS[bucketIndex]; + } + /** Check if it's been a while since last parole and let idle apps do some work */ void checkParoleTimeout() { boolean setParoled = false; synchronized (mAppIdleLock) { - final long now = System.currentTimeMillis(); + final long now = mInjector.currentTimeMillis(); if (!mAppIdleTempParoled) { final long timeSinceLastParole = now - mLastAppIdleParoledTime; if (timeSinceLastParole > mAppIdleParoleIntervalMillis) { @@ -374,10 +446,10 @@ public class AppStandbyController { final int uid = mPackageManager.getPackageUidAsUser(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); if (idle) { - mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, + mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE, packageName, uid); } else { - mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, + mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE, packageName, uid); } } catch (PackageManager.NameNotFoundException | RemoteException e) { @@ -389,7 +461,7 @@ public class AppStandbyController { if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle); boolean paroled = false; synchronized (mAppIdleLock) { - final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime; + final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime; if (!deviceIdle && timeSinceLastParole >= mAppIdleParoleIntervalMillis) { if (DEBUG) { @@ -419,13 +491,11 @@ public class AppStandbyController { || event.mEventType == UsageEvents.Event.USER_INTERACTION)) { mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime); if (previouslyIdle) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId, - /* idle = */ 0, event.mPackage)); + maybeInformListeners(event.mPackage, userId, elapsedRealtime, false); notifyBatteryStats(event.mPackage, userId, false); } } } - } /** @@ -439,7 +509,7 @@ public class AppStandbyController { void forceIdleState(String packageName, int userId, boolean idle) { final int appId = getAppId(packageName); if (appId < 0) return; - final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long elapsedRealtime = mInjector.elapsedRealtime(); final boolean previouslyIdle = isAppIdleFiltered(packageName, appId, userId, elapsedRealtime); @@ -470,7 +540,7 @@ public class AppStandbyController { } } - void addListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) { + void addListener(AppIdleStateChangeListener listener) { synchronized (mAppIdleLock) { if (!mPackageAccessListeners.contains(listener)) { mPackageAccessListeners.add(listener); @@ -478,7 +548,7 @@ public class AppStandbyController { } } - void removeListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) { + void removeListener(AppIdleStateChangeListener listener) { synchronized (mAppIdleLock) { mPackageAccessListeners.remove(listener); } @@ -501,75 +571,79 @@ public class AppStandbyController { return false; } if (shouldObfuscateInstantApps && - mPackageManagerInternal.isPackageEphemeral(userId, packageName)) { + mInjector.isPackageEphemeral(userId, packageName)) { return false; } return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime); } - /** - * Checks if an app has been idle for a while and filters out apps that are excluded. - * It returns false if the current system state allows all apps to be considered active. - * This happens if the device is plugged in or temporarily allowed to make exceptions. - * Called by interface impls. - */ - boolean isAppIdleFiltered(String packageName, int appId, int userId, - long elapsedRealtime) { + /** Returns true if this app should be whitelisted for some reason, to never go into standby */ + boolean isAppSpecial(String packageName, int appId, int userId) { if (packageName == null) return false; // If not enabled at all, of course nobody is ever idle. if (!mAppIdleEnabled) { - return false; + return true; } if (appId < Process.FIRST_APPLICATION_UID) { // System uids never go idle. - return false; + return true; } if (packageName.equals("android")) { // Nor does the framework (which should be redundant with the above, but for MR1 we will // retain this for safety). - return false; + return true; } if (mSystemServicesReady) { try { // We allow all whitelisted apps, including those that don't want to be whitelisted // for idle mode, because app idle (aka app standby) is really not as big an issue // for controlling who participates vs. doze mode. - if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) { - return false; + if (mInjector.isPowerSaveWhitelistExceptIdleApp(packageName)) { + return true; } } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } if (isActiveDeviceAdmin(packageName, userId)) { - return false; + return true; } if (isActiveNetworkScorer(packageName)) { - return false; + return true; } if (mAppWidgetManager != null - && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) { - return false; + && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) { + return true; } if (isDeviceProvisioningPackage(packageName)) { - return false; + return true; } } - if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) { - return false; + // Check this last, as it can be the most expensive check + if (isCarrierApp(packageName)) { + return true; } - // Check this last, as it is the most expensive check - // TODO: Optimize this by fetching the carrier privileged apps ahead of time - if (isCarrierApp(packageName)) { + return false; + } + + /** + * Checks if an app has been idle for a while and filters out apps that are excluded. + * It returns false if the current system state allows all apps to be considered active. + * This happens if the device is plugged in or temporarily allowed to make exceptions. + * Called by interface impls. + */ + boolean isAppIdleFiltered(String packageName, int appId, int userId, + long elapsedRealtime) { + if (isAppSpecial(packageName, appId, userId)) { return false; + } else { + return isAppIdleUnfiltered(packageName, userId, elapsedRealtime); } - - return true; } int[] getIdleUidsForUser(int userId) { @@ -577,7 +651,7 @@ public class AppStandbyController { return new int[0]; } - final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long elapsedRealtime = mInjector.elapsedRealtime(); List<ApplicationInfo> apps; try { @@ -613,7 +687,7 @@ public class AppStandbyController { } } if (DEBUG) { - Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime)); + Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime)); } int numIdle = 0; for (int i = uidStates.size() - 1; i >= 0; i--) { @@ -643,6 +717,21 @@ public class AppStandbyController { .sendToTarget(); } + @StandbyBuckets int getAppStandbyBucket(String packageName, int userId, + long elapsedRealtime, boolean shouldObfuscateInstantApps) { + if (shouldObfuscateInstantApps && + mInjector.isPackageEphemeral(userId, packageName)) { + return AppStandby.STANDBY_BUCKET_ACTIVE; + } + + return mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime); + } + + void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, + String reason, long elapsedRealtime) { + mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason); + } + private boolean isActiveDeviceAdmin(String packageName, int userId) { DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); if (dpm == null) return false; @@ -662,7 +751,7 @@ public class AppStandbyController { private boolean isCarrierApp(String packageName) { synchronized (mAppIdleLock) { if (!mHaveCarrierPrivilegedApps) { - fetchCarrierPrivilegedAppsLA(); + fetchCarrierPrivilegedAppsLocked(); } if (mCarrierPrivilegedApps != null) { return mCarrierPrivilegedApps.contains(packageName); @@ -682,7 +771,7 @@ public class AppStandbyController { } @GuardedBy("mAppIdleLock") - private void fetchCarrierPrivilegedAppsLA() { + private void fetchCarrierPrivilegedAppsLocked() { TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges(); @@ -693,20 +782,19 @@ public class AppStandbyController { } private boolean isActiveNetworkScorer(String packageName) { - NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService( - Context.NETWORK_SCORE_SERVICE); - return packageName != null && packageName.equals(nsm.getActiveScorerPackage()); + String activeScorer = mInjector.getActiveNetworkScorer(); + return packageName != null && packageName.equals(activeScorer); } void informListeners(String packageName, int userId, boolean isIdle) { - for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) { + for (AppIdleStateChangeListener listener : mPackageAccessListeners) { listener.onAppIdleStateChanged(packageName, userId, isIdle); } } void informParoleStateChanged() { final boolean paroled = isParoledOrCharging(); - for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) { + for (AppIdleStateChangeListener listener : mPackageAccessListeners) { listener.onParoleStateChanged(paroled); } } @@ -726,8 +814,7 @@ public class AppStandbyController { } boolean isDisplayOn() { - return mDisplayManager - .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON; + return mInjector.isDefaultDisplayOn(); } void clearAppIdleForPackage(String packageName, int userId) { @@ -755,7 +842,7 @@ public class AppStandbyController { void initializeDefaultsForSystemApps(int userId) { Slog.d(TAG, "Initializing defaults for system apps on user " + userId); - final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long elapsedRealtime = mInjector.elapsedRealtime(); List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( PackageManager.MATCH_DISABLED_COMPONENTS, userId); @@ -786,9 +873,9 @@ public class AppStandbyController { } } - void dumpUser(IndentingPrintWriter idpw, int userId) { + void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) { synchronized (mAppIdleLock) { - mAppIdleHistory.dump(idpw, userId); + mAppIdleHistory.dump(idpw, userId, pkg); } } @@ -828,6 +915,116 @@ public class AppStandbyController { pw.print(" mLastAppIdleParoledTime="); TimeUtils.formatDuration(mLastAppIdleParoledTime, pw); pw.println(); + pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds)); + pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds)); + } + + /** + * Injector for interaction with external code. Override methods to provide a mock + * implementation for tests. + * onBootPhase() must be called with at least the PHASE_SYSTEM_SERVICES_READY + */ + static class Injector { + + private final Context mContext; + private final Looper mLooper; + private IDeviceIdleController mDeviceIdleController; + private IBatteryStats mBatteryStats; + private PackageManagerInternal mPackageManagerInternal; + private DisplayManager mDisplayManager; + int mBootPhase; + + Injector(Context context, Looper looper) { + mContext = context; + mLooper = looper; + } + + Context getContext() { + return mContext; + } + + Looper getLooper() { + return mLooper; + } + + void onBootPhase(int phase) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + mDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + mBatteryStats = IBatteryStats.Stub.asInterface( + ServiceManager.getService(BatteryStats.SERVICE_NAME)); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mDisplayManager = (DisplayManager) mContext.getSystemService( + Context.DISPLAY_SERVICE); + } + mBootPhase = phase; + } + + int getBootPhase() { + return mBootPhase; + } + + /** + * Returns the elapsed realtime since the device started. Override this + * to control the clock. + * @return elapsed realtime + */ + long elapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + + long currentTimeMillis() { + return System.currentTimeMillis(); + } + + boolean isAppIdleEnabled() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableAutoPowerModes); + } + + boolean isCharging() { + return mContext.getSystemService(BatteryManager.class).isCharging(); + } + + boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { + return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName); + } + + File getDataSystemDirectory() { + return Environment.getDataSystemDirectory(); + } + + void noteEvent(int event, String packageName, int uid) throws RemoteException { + mBatteryStats.noteEvent(event, packageName, uid); + } + + boolean isPackageEphemeral(int userId, String packageName) { + return mPackageManagerInternal.isPackageEphemeral(userId, packageName); + } + + int[] getRunningUserIds() throws RemoteException { + return ActivityManager.getService().getRunningUserIds(); + } + + boolean isDefaultDisplayOn() { + return mDisplayManager + .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON; + } + + void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) { + mDisplayManager.registerDisplayListener(listener, handler); + } + + String getActiveNetworkScorer() { + NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService( + Context.NETWORK_SCORE_SERVICE); + return nsm.getActiveScorerPackage(); + } + + public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName, + int userId) { + return appWidgetManager.isBoundWidgetPackage(packageName, userId); + } } class AppStandbyHandler extends Handler { @@ -839,6 +1036,10 @@ public class AppStandbyController { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MSG_INFORM_LISTENERS: + informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1); + break; + case MSG_FORCE_IDLE_STATE: forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1); break; @@ -911,7 +1112,7 @@ public class AppStandbyController { if (displayId == Display.DEFAULT_DISPLAY) { final boolean displayOn = isDisplayOn(); synchronized (mAppIdleLock) { - mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime()); + mAppIdleHistory.updateDisplay(displayOn, mInjector.elapsedRealtime()); } } } @@ -931,6 +1132,8 @@ public class AppStandbyController { private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold"; private static final String KEY_PAROLE_INTERVAL = "parole_interval"; private static final String KEY_PAROLE_DURATION = "parole_duration"; + private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds"; + private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds"; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -969,7 +1172,7 @@ public class AppStandbyController { COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4, - COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours + COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours // Default: 24 hours between paroles mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL, @@ -979,9 +1182,35 @@ public class AppStandbyController { 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, + SCREEN_TIME_THRESHOLDS); + + String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null); + mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue, + ELAPSED_TIME_THRESHOLDS); } } - } + long[] parseLongArray(String values, long[] defaults) { + if (values == null) return defaults; + if (values.isEmpty()) { + // Reset to defaults + return defaults; + } else { + String[] thresholds = values.split("/"); + if (thresholds.length == THRESHOLD_BUCKETS.length) { + long[] array = new long[THRESHOLD_BUCKETS.length]; + for (int i = 0; i < THRESHOLD_BUCKETS.length; i++) { + array[i] = Long.parseLong(thresholds[i]); + } + return array; + } else { + return defaults; + } + } + } + } } diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java index afafea19..44e6a6cb 100644 --- a/com/android/server/usage/UsageStatsService.java +++ b/com/android/server/usage/UsageStatsService.java @@ -20,6 +20,7 @@ import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.IUidObserver; +import android.app.usage.AppStandby; import android.app.usage.ConfigurationStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; @@ -469,8 +470,32 @@ public class UsageStatsService extends SystemService implements void dump(String[] args, PrintWriter pw) { synchronized (mLock) { IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " "); - ArraySet<String> argSet = new ArraySet<>(); - argSet.addAll(Arrays.asList(args)); + + boolean checkin = false; + boolean history = false; + String pkg = null; + + if (args != null) { + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if ("--checkin".equals(arg)) { + checkin = true; + } else if ("--history".equals(arg)) { + history = true; + } else if ("history".equals(arg)) { + history = true; + break; + } else if ("flush".equals(arg)) { + flushToDiskLocked(); + pw.println("Flushed stats to disk"); + return; + } else { + // Anything else is a pkg to filter + pkg = arg; + break; + } + } + } final int userCount = mUserState.size(); for (int i = 0; i < userCount; i++) { @@ -478,26 +503,23 @@ public class UsageStatsService extends SystemService implements idpw.printPair("user", userId); idpw.println(); idpw.increaseIndent(); - if (argSet.contains("--checkin")) { + if (checkin) { mUserState.valueAt(i).checkin(idpw); } else { - mUserState.valueAt(i).dump(idpw); + mUserState.valueAt(i).dump(idpw, pkg); idpw.println(); - if (args.length > 0) { - if ("history".equals(args[0])) { - mAppStandby.dumpHistory(idpw, userId); - } else if ("flush".equals(args[0])) { - flushToDiskLocked(); - pw.println("Flushed stats to disk"); - } + if (history) { + mAppStandby.dumpHistory(idpw, userId); } } - mAppStandby.dumpUser(idpw, userId); + mAppStandby.dumpUser(idpw, userId, pkg); idpw.decreaseIndent(); } - pw.println(); - mAppStandby.dumpState(args, pw); + if (pkg == null) { + pw.println(); + mAppStandby.dumpState(args, pw); + } } } @@ -654,6 +676,55 @@ public class UsageStatsService extends SystemService implements } @Override + public int getAppStandbyBucket(String packageName, String callingPackage, int userId) { + if (!hasPermission(callingPackage)) { + throw new SecurityException("Don't have permission to query app standby bucket"); + } + + final int callingUid = Binder.getCallingUid(); + try { + userId = ActivityManager.getService().handleIncomingUser( + Binder.getCallingPid(), callingUid, userId, false, true, + "getAppStandbyBucket", null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(callingUid, + userId); + final long token = Binder.clearCallingIdentity(); + try { + return mAppStandby.getAppStandbyBucket(packageName, userId, + SystemClock.elapsedRealtime(), obfuscateInstantApps); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setAppStandbyBucket(String packageName, + int bucket, int userId) { + getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE, + "No permission to change app standby state"); + + final int callingUid = Binder.getCallingUid(); + try { + userId = ActivityManager.getService().handleIncomingUser( + Binder.getCallingPid(), callingUid, userId, false, true, + "setAppStandbyBucket", null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + final long token = Binder.clearCallingIdentity(); + try { + mAppStandby.setAppStandbyBucket(packageName, userId, bucket, + AppStandby.REASON_PREDICTED + ":" + callingUid, + SystemClock.elapsedRealtime()); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void whitelistAppTemporarily(String packageName, long duration, int userId) throws RemoteException { StringBuilder reason = new StringBuilder(32); diff --git a/com/android/server/usage/UserUsageStatsService.java b/com/android/server/usage/UserUsageStatsService.java index 0abbb82a..0b105904 100644 --- a/com/android/server/usage/UserUsageStatsService.java +++ b/com/android/server/usage/UserUsageStatsService.java @@ -474,19 +474,19 @@ class UserUsageStatsService { mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() { @Override public boolean checkin(IntervalStats stats) { - printIntervalStats(pw, stats, false); + printIntervalStats(pw, stats, false, null); return true; } }); } - void dump(IndentingPrintWriter pw) { + void dump(IndentingPrintWriter pw, String pkg) { // This is not a check-in, only dump in-memory stats. for (int interval = 0; interval < mCurrentStats.length; interval++) { pw.print("In-memory "); pw.print(intervalToString(interval)); pw.println(" stats"); - printIntervalStats(pw, mCurrentStats[interval], true); + printIntervalStats(pw, mCurrentStats[interval], true, pkg); } } @@ -505,7 +505,7 @@ class UserUsageStatsService { } void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, - boolean prettyDates) { + boolean prettyDates, String pkg) { if (prettyDates) { pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, stats.beginTime, stats.endTime, sDateFormatFlags) + "\""); @@ -521,6 +521,9 @@ class UserUsageStatsService { final int pkgCount = pkgStats.size(); for (int i = 0; i < pkgCount; i++) { final UsageStats usageStats = pkgStats.valueAt(i); + if (pkg != null && !pkg.equals(usageStats.mPackageName)) { + continue; + } pw.printPair("package", usageStats.mPackageName); pw.printPair("totalTime", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); @@ -533,6 +536,9 @@ class UserUsageStatsService { pw.println("ChooserCounts"); pw.increaseIndent(); for (UsageStats usageStats : pkgStats.values()) { + if (pkg != null && !pkg.equals(usageStats.mPackageName)) { + continue; + } pw.printPair("package", usageStats.mPackageName); if (usageStats.mChooserCounts != null) { final int chooserCountSize = usageStats.mChooserCounts.size(); @@ -555,19 +561,22 @@ class UserUsageStatsService { } pw.decreaseIndent(); - pw.println("configurations"); - pw.increaseIndent(); - final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; - final int configCount = configStats.size(); - for (int i = 0; i < configCount; i++) { - final ConfigurationStats config = configStats.valueAt(i); - pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration)); - pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); - pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); - pw.printPair("count", config.mActivationCount); - pw.println(); + if (pkg == null) { + pw.println("configurations"); + pw.increaseIndent(); + final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; + final int configCount = configStats.size(); + for (int i = 0; i < configCount; i++) { + final ConfigurationStats config = configStats.valueAt(i); + pw.printPair("config", Configuration.resourceQualifierString( + config.mConfiguration)); + pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); + pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); + pw.printPair("count", config.mActivationCount); + pw.println(); + } + pw.decreaseIndent(); } - pw.decreaseIndent(); pw.println("events"); pw.increaseIndent(); @@ -575,6 +584,9 @@ class UserUsageStatsService { final int eventCount = events != null ? events.size() : 0; for (int i = 0; i < eventCount; i++) { final UsageEvents.Event event = events.valueAt(i); + if (pkg != null && !pkg.equals(event.mPackage)) { + continue; + } pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); pw.printPair("type", eventToString(event.mEventType)); pw.printPair("package", event.mPackage); diff --git a/com/android/server/utils/PriorityDump.java b/com/android/server/utils/PriorityDump.java index 054f1564..fb92c2b8 100644 --- a/com/android/server/utils/PriorityDump.java +++ b/com/android/server/utils/PriorityDump.java @@ -16,12 +16,19 @@ package com.android.server.utils; +import android.annotation.IntDef; + import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; /** * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the - * {@link #PRIORITY_ARG} argument. + * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments. * <p> * Typical usage: * @@ -31,13 +38,25 @@ public class SpringfieldNuclearPowerPlant extends Binder { private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { @Override - public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("Donuts in the box: 1"); + public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + if (asProto) { + ProtoOutputStream proto = new ProtoOutputStream(fd); + proto.write(SpringfieldProto.DONUTS, 1); + proto.flush(); + } else { + pw.println("Donuts in the box: 1"); + } } @Override public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT"); + if (asProto) { + ProtoOutputStream proto = new ProtoOutputStream(fd); + proto.write(SpringfieldProto.REACTOR_STATUS, DANGER_MELTDOWN_IMMINENT); + proto.flush(); + } else { + pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT"); + } } }; @@ -65,6 +84,9 @@ public class SpringfieldNuclearPowerPlant extends Binder { $ adb shell dumpsys snpp --dump-priority NORMAL Nuclear reactor status: DANGER - MELTDOWN IMMINENT + $ adb shell dumpsys snpp --dump-priority CRITICAL --proto + //binary output + * </code></pre> * * @@ -85,95 +107,146 @@ public class SpringfieldNuclearPowerPlant extends Binder { public final class PriorityDump { public static final String PRIORITY_ARG = "--dump-priority"; + public static final String PROTO_ARG = "--proto"; private PriorityDump() { throw new UnsupportedOperationException(); } + /** Enum to switch through supported priority types */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({PRIORITY_TYPE_INVALID, PRIORITY_TYPE_CRITICAL, PRIORITY_TYPE_HIGH, + PRIORITY_TYPE_NORMAL}) + private @interface PriorityType { } + private static final int PRIORITY_TYPE_INVALID = 0; + private static final int PRIORITY_TYPE_CRITICAL = 1; + private static final int PRIORITY_TYPE_HIGH = 2; + private static final int PRIORITY_TYPE_NORMAL = 3; + /** - * Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is - * {@code --dump-priority}, stripping the priority and its type. + * Parses {@code args} matching {@code --dump-priority} and/or {@code --proto}. The matching + * arguments are stripped. + * <p> + * If priority args are passed as an argument, it will call the appropriate method and if proto + * args are passed then the {@code asProto} flag is set. * <p> * For example, if called as {@code --dump-priority HIGH arg1 arg2 arg3}, it will call - * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) </code> + * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}, false) </code> * <p> * If the {@code --dump-priority} is not set, it calls - * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole + * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[], boolean)} passing the whole * {@code args} instead. */ public static void dump(PriorityDumper dumper, FileDescriptor fd, PrintWriter pw, String[] args) { - if (args != null && args.length >= 2 && args[0].equals(PRIORITY_ARG)) { - final String priority = args[1]; - switch (priority) { - case "CRITICAL": { - dumper.dumpCritical(fd, pw, getStrippedArgs(args)); - return; - } - case "HIGH": { - dumper.dumpHigh(fd, pw, getStrippedArgs(args)); - return; - } - case "NORMAL": { - dumper.dumpNormal(fd, pw, getStrippedArgs(args)); - return; + boolean asProto = false; + @PriorityType int priority = PRIORITY_TYPE_INVALID; + + if (args == null) { + dumper.dump(fd, pw, args, asProto); + return; + } + + String[] strippedArgs = new String[args.length]; + int strippedCount = 0; + for (int argIndex = 0; argIndex < args.length; argIndex++) { + if (args[argIndex].equals(PROTO_ARG)) { + asProto = true; + } else if (args[argIndex].equals(PRIORITY_ARG)) { + if (argIndex + 1 < args.length) { + argIndex++; + priority = getPriorityType(args[argIndex]); } + } else { + strippedArgs[strippedCount++] = args[argIndex]; + } + } + + if (strippedCount < args.length) { + strippedArgs = Arrays.copyOf(strippedArgs, strippedCount); + } + + switch (priority) { + case PRIORITY_TYPE_CRITICAL: { + dumper.dumpCritical(fd, pw, strippedArgs, asProto); + return; + } + case PRIORITY_TYPE_HIGH: { + dumper.dumpHigh(fd, pw, strippedArgs, asProto); + return; + } + case PRIORITY_TYPE_NORMAL: { + dumper.dumpNormal(fd, pw, strippedArgs, asProto); + return; + } + default: { + dumper.dump(fd, pw, strippedArgs, asProto); + return; } } - dumper.dump(fd, pw, args); } /** - * Gets an array without the {@code --dump-priority PRIORITY} prefix. + * Converts priority argument type to enum. */ - private static String[] getStrippedArgs(String[] args) { - final String[] stripped = new String[args.length - 2]; - System.arraycopy(args, 2, stripped, 0, stripped.length); - return stripped; + private static @PriorityType int getPriorityType(String arg) { + switch (arg) { + case "CRITICAL": { + return PRIORITY_TYPE_CRITICAL; + } + case "HIGH": { + return PRIORITY_TYPE_HIGH; + } + case "NORMAL": { + return PRIORITY_TYPE_NORMAL; + } + } + return PRIORITY_TYPE_INVALID; } /** * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the - * {@link #PRIORITY_ARG} argument. + * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments. * * @hide */ - public static interface PriorityDumper { + public interface PriorityDumper { /** * Dumps only the critical section. */ @SuppressWarnings("unused") - default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) { + default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, + boolean asProto) { } /** * Dumps only the high-priority section. */ @SuppressWarnings("unused") - default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) { + default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { } /** * Dumps only the normal section. */ @SuppressWarnings("unused") - default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) { + default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { } /** * Dumps all sections. * <p> * This method is called when - * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[])} is - * called without priority arguments. By default, it calls the 3 {@code dumpTYPE} methods, - * so sub-classes just need to implement the priority types they support. + * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[], boolean)} + * is called without priority arguments. By default, it calls the 3 {@code dumpTYPE} + * methods, so sub-classes just need to implement the priority types they support. */ @SuppressWarnings("unused") - default void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - dumpCritical(fd, pw, args); - dumpHigh(fd, pw, args); - dumpNormal(fd, pw, args); + default void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + dumpCritical(fd, pw, args, asProto); + dumpHigh(fd, pw, args, asProto); + dumpNormal(fd, pw, args, asProto); } } } diff --git a/com/android/server/wifi/FrameworkFacade.java b/com/android/server/wifi/FrameworkFacade.java index 760ee697..2c164448 100644 --- a/com/android/server/wifi/FrameworkFacade.java +++ b/com/android/server/wifi/FrameworkFacade.java @@ -25,7 +25,7 @@ import android.content.Intent; import android.database.ContentObserver; import android.net.TrafficStats; import android.net.Uri; -import android.net.ip.IpManager; +import android.net.ip.IpClient; import android.os.BatteryStats; import android.os.Handler; import android.os.IBinder; @@ -137,9 +137,9 @@ public class FrameworkFacade { return TrafficStats.getRxPackets(iface); } - public IpManager makeIpManager( - Context context, String iface, IpManager.Callback callback) { - return new IpManager(context, iface, callback); + public IpClient makeIpClient( + Context context, String iface, IpClient.Callback callback) { + return new IpClient(context, iface, callback); } /** diff --git a/com/android/server/wifi/HalDeviceManager.java b/com/android/server/wifi/HalDeviceManager.java index d6009c70..7a1e8d9e 100644 --- a/com/android/server/wifi/HalDeviceManager.java +++ b/com/android/server/wifi/HalDeviceManager.java @@ -396,43 +396,48 @@ public class HalDeviceManager { } /** - * Creates a IWifiRttController corresponding to the input interface. A direct match to the - * IWifiChip.createRttController() method. + * Creates a IWifiRttController. A direct match to the IWifiChip.createRttController() method. * * Returns the created IWifiRttController or a null on error. */ - public IWifiRttController createRttController(IWifiIface boundIface) { - if (DBG) Log.d(TAG, "createRttController: boundIface(name)=" + getName(boundIface)); + public IWifiRttController createRttController() { + if (DBG) Log.d(TAG, "createRttController"); synchronized (mLock) { if (mWifi == null) { - Log.e(TAG, "createRttController: null IWifi -- boundIface(name)=" - + getName(boundIface)); + Log.e(TAG, "createRttController: null IWifi"); return null; } - IWifiChip chip = getChip(boundIface); - if (chip == null) { - Log.e(TAG, "createRttController: null IWifiChip -- boundIface(name)=" - + getName(boundIface)); + WifiChipInfo[] chipInfos = getAllChipInfo(); + if (chipInfos == null) { + Log.e(TAG, "createRttController: no chip info found"); + stopWifi(); // major error: shutting down return null; } - Mutable<IWifiRttController> rttResp = new Mutable<>(); - try { - chip.createRttController(boundIface, - (WifiStatus status, IWifiRttController rtt) -> { - if (status.code == WifiStatusCode.SUCCESS) { - rttResp.value = rtt; - } else { - Log.e(TAG, "IWifiChip.createRttController failed: " + statusString( - status)); - } - }); - } catch (RemoteException e) { - Log.e(TAG, "IWifiChip.createRttController exception: " + e); + for (WifiChipInfo chipInfo : chipInfos) { + Mutable<IWifiRttController> rttResp = new Mutable<>(); + try { + chipInfo.chip.createRttController(null, + (WifiStatus status, IWifiRttController rtt) -> { + if (status.code == WifiStatusCode.SUCCESS) { + rttResp.value = rtt; + } else { + Log.e(TAG, + "IWifiChip.createRttController failed: " + statusString( + status)); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "IWifiChip.createRttController exception: " + e); + } + if (rttResp.value != null) { + return rttResp.value; + } } - return rttResp.value; + Log.e(TAG, "createRttController: not available from any of the chips"); + return null; } } diff --git a/com/android/server/wifi/WifiConfigManager.java b/com/android/server/wifi/WifiConfigManager.java index ba1695a5..b610bd98 100644 --- a/com/android/server/wifi/WifiConfigManager.java +++ b/com/android/server/wifi/WifiConfigManager.java @@ -1010,7 +1010,7 @@ public class WifiConfigManager { } boolean newNetwork = (existingInternalConfig == null); - // This is needed to inform IpManager about any IP configuration changes. + // This is needed to inform IpClient about any IP configuration changes. boolean hasIpChanged = newNetwork || WifiConfigurationUtil.hasIpChanged( existingInternalConfig, newInternalConfig); diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java index 35dec2e7..eefd2f0d 100644 --- a/com/android/server/wifi/WifiNative.java +++ b/com/android/server/wifi/WifiNative.java @@ -80,10 +80,6 @@ public class WifiNative { return mInterfaceName; } - public WifiVendorHal getVendorHal() { - return mWifiVendorHal; - } - /** * Enable verbose logging for all sub modules. */ diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java index 0276ff43..dacc2d47 100644 --- a/com/android/server/wifi/WifiServiceImpl.java +++ b/com/android/server/wifi/WifiServiceImpl.java @@ -61,7 +61,7 @@ import android.net.Network; import android.net.NetworkUtils; import android.net.StaticIpConfiguration; import android.net.Uri; -import android.net.ip.IpManager; +import android.net.ip.IpClient; import android.net.wifi.IWifiManager; import android.net.wifi.ScanResult; import android.net.wifi.ScanSettings; @@ -857,32 +857,6 @@ public class WifiServiceImpl extends IWifiManager.Stub { } /** - * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)} - * @param wifiConfig SSID, security and channel details as - * part of WifiConfiguration - * @param enabled true to enable and false to disable - */ - @Override - public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { - enforceChangePermission(); - mWifiPermissionsUtil.enforceTetherChangePermission(mContext); - - mLog.info("setWifiApEnabled uid=% enable=%").c(Binder.getCallingUid()).c(enabled).flush(); - - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) { - throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user."); - } - // null wifiConfig is a meaningful input for CMD_SET_AP - if (wifiConfig == null || isValid(wifiConfig)) { - int mode = WifiManager.IFACE_IP_MODE_UNSPECIFIED; - SoftApModeConfiguration softApConfig = new SoftApModeConfiguration(mode, wifiConfig); - mWifiController.sendMessage(CMD_SET_AP, enabled ? 1 : 0, 0, softApConfig); - } else { - Slog.e(TAG, "Invalid WifiConfiguration"); - } - } - - /** * see {@link WifiManager#getWifiApState()} * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED}, * {@link WifiManager#WIFI_AP_STATE_DISABLING}, @@ -2304,11 +2278,11 @@ public class WifiServiceImpl extends IWifiManager.Stub { // WifiMetrics proto bytes were requested. Dump only these. mWifiStateMachine.updateWifiMetrics(); mWifiMetrics.dump(fd, pw, args); - } else if (args != null && args.length > 0 && IpManager.DUMP_ARG.equals(args[0])) { - // IpManager dump was requested. Pass it along and take no further action. - String[] ipManagerArgs = new String[args.length - 1]; - System.arraycopy(args, 1, ipManagerArgs, 0, ipManagerArgs.length); - mWifiStateMachine.dumpIpManager(fd, pw, ipManagerArgs); + } else if (args != null && args.length > 0 && IpClient.DUMP_ARG.equals(args[0])) { + // IpClient dump was requested. Pass it along and take no further action. + String[] ipClientArgs = new String[args.length - 1]; + System.arraycopy(args, 1, ipClientArgs, 0, ipClientArgs.length); + mWifiStateMachine.dumpIpClient(fd, pw, ipClientArgs); } else if (args != null && args.length > 0 && WifiScoreReport.DUMP_ARG.equals(args[0])) { WifiScoreReport wifiScoreReport = mWifiStateMachine.getWifiScoreReport(); if (wifiScoreReport != null) wifiScoreReport.dump(fd, pw, args); diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java index b7152acd..8e133133 100644 --- a/com/android/server/wifi/WifiStateMachine.java +++ b/com/android/server/wifi/WifiStateMachine.java @@ -59,7 +59,7 @@ import android.net.RouteInfo; import android.net.StaticIpConfiguration; import android.net.TrafficStats; import android.net.dhcp.DhcpClient; -import android.net.ip.IpManager; +import android.net.ip.IpClient; import android.net.wifi.IApInterface; import android.net.wifi.IClientInterface; import android.net.wifi.RssiPacketCountInfo; @@ -447,7 +447,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss return true; } - private final IpManager mIpManager; + private final IpClient mIpClient; // Channel for sending replies. private AsyncChannel mReplyChannel = new AsyncChannel(); @@ -705,7 +705,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss static final int CMD_GET_ALL_MATCHING_CONFIGS = BASE + 168; /** - * Used to handle messages bounced between WifiStateMachine and IpManager. + * Used to handle messages bounced between WifiStateMachine and IpClient. */ static final int CMD_IPV4_PROVISIONING_SUCCESS = BASE + 200; static final int CMD_IPV4_PROVISIONING_FAILURE = BASE + 201; @@ -959,8 +959,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID; mLastSignalLevel = -1; - mIpManager = mFacade.makeIpManager(mContext, mInterfaceName, new IpManagerCallback()); - mIpManager.setMulticastFilter(true); + mIpClient = mFacade.makeIpClient(mContext, mInterfaceName, new IpClientCallback()); + mIpClient.setMulticastFilter(true); mNoNetworksPeriodicScan = mContext.getResources().getInteger( R.integer.config_wifi_no_network_periodic_scan_interval); @@ -1139,7 +1139,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } - class IpManagerCallback extends IpManager.Callback { + class IpClientCallback extends IpClient.Callback { @Override public void onPreDhcpAction() { sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION); @@ -1202,10 +1202,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss } } - private void stopIpManager() { + private void stopIpClient() { /* Restore power save and suspend optimizations */ handlePostDhcpSetup(); - mIpManager.stop(); + mIpClient.stop(); } PendingIntent getPrivateBroadcast(String action, int requestCode) { @@ -2109,14 +2109,14 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss * Start filtering Multicast v4 packets */ public void startFilteringMulticastPackets() { - mIpManager.setMulticastFilter(true); + mIpClient.setMulticastFilter(true); } /** * Stop filtering Multicast v4 packets */ public void stopFilteringMulticastPackets() { - mIpManager.setMulticastFilter(false); + mIpClient.setMulticastFilter(false); } /** @@ -2232,8 +2232,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss } } - public void dumpIpManager(FileDescriptor fd, PrintWriter pw, String[] args) { - mIpManager.dump(fd, pw, args); + public void dumpIpClient(FileDescriptor fd, PrintWriter pw, String[] args) { + mIpClient.dump(fd, pw, args); } @Override @@ -2280,7 +2280,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss pw.println(); mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_USER_ACTION); mWifiDiagnostics.dump(fd, pw, args); - dumpIpManager(fd, pw, args); + dumpIpClient(fd, pw, args); if (mWifiConnectivityManager != null) { mWifiConnectivityManager.dump(fd, pw, args); } else { @@ -3066,7 +3066,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss log("Link configuration changed for netId: " + mLastNetworkId + " old: " + mLinkProperties + " new: " + newLp); } - // We own this instance of LinkProperties because IpManager passes us a copy. + // We own this instance of LinkProperties because IpClient passes us a copy. mLinkProperties = newLp; if (mNetworkAgent != null) { mNetworkAgent.sendLinkProperties(mLinkProperties); @@ -3096,7 +3096,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss */ private void clearLinkProperties() { // Clear the link properties obtained from DHCP. The only caller of this - // function has already called IpManager#stop(), which clears its state. + // function has already called IpClient#stop(), which clears its state. synchronized (mDhcpResultsLock) { if (mDhcpResults != null) { mDhcpResults.clear(); @@ -3322,7 +3322,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss clearTargetBssid("handleNetworkDisconnect"); - stopIpManager(); + stopIpClient(); /* Reset data structures */ mWifiScoreReport.reset(); @@ -3453,7 +3453,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss // short in two ways: // - at the time of the CMD_IP_CONFIGURATION_SUCCESSFUL event, we don't know if we // actually have ARP reachability. it might be better to wait until the wifi - // network has been validated by IpManager. + // network has been validated by IpClient. // - in the case of a roaming event (intra-SSID), we probably trigger when L2 is // complete. // @@ -4378,7 +4378,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss // Disable legacy multicast filtering, which on some chipsets defaults to enabled. // Legacy IPv6 multicast filtering blocks ICMPv6 router advertisements which breaks IPv6 // provisioning. Legacy IPv4 multicast filtering may be re-enabled later via - // IpManager.Callback.setFallbackMulticastFilter() + // IpClient.Callback.setFallbackMulticastFilter() mWifiNative.stopFilteringMulticastV4Packets(); mWifiNative.stopFilteringMulticastV6Packets(); @@ -5043,7 +5043,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss // DNAv4/DNAv6 -style probing for on-link neighbors of // interest (e.g. routers); harmless if none are configured. if (state == SupplicantState.COMPLETED) { - mIpManager.confirmConfiguration(); + mIpClient.confirmConfiguration(); } break; case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST: @@ -5314,7 +5314,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss } else { if (result.hasProxyChanged()) { log("Reconfiguring proxy on connection"); - mIpManager.setHttpProxy( + mIpClient.setHttpProxy( getCurrentWifiConfiguration().getHttpProxy()); } if (result.hasIpChanged()) { @@ -5783,7 +5783,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss @Override public void exit() { - mIpManager.stop(); + mIpClient.stop(); // This is handled by receiving a NETWORK_DISCONNECTION_EVENT in ConnectModeState // Bug: 15347363 @@ -5813,17 +5813,17 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss handlePreDhcpSetup(); break; case DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE: - mIpManager.completedPreDhcpAction(); + mIpClient.completedPreDhcpAction(); break; case DhcpClient.CMD_POST_DHCP_ACTION: handlePostDhcpSetup(); - // We advance to mConnectedState because IpManager will also send a + // We advance to mConnectedState because IpClient will also send a // CMD_IPV4_PROVISIONING_SUCCESS message, which calls handleIPv4Success(), // which calls updateLinkProperties, which then sends // CMD_IP_CONFIGURATION_SUCCESSFUL. // // In the event of failure, we transition to mDisconnectingState - // similarly--via messages sent back from IpManager. + // similarly--via messages sent back from IpClient. break; case CMD_IPV4_PROVISIONING_SUCCESS: { handleIPv4Success((DhcpResults) message.obj); @@ -6065,7 +6065,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss // cause the roam to fail and the device to disconnect. clearTargetBssid("ObtainingIpAddress"); - // Stop IpManager in case we're switching from DHCP to static + // Stop IpClient in case we're switching from DHCP to static // configuration or vice versa. // // TODO: Only ever enter this state the first time we connect to a @@ -6075,15 +6075,15 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss // disconnected, because DHCP might take a long time during which // connectivity APIs such as getActiveNetworkInfo should not return // CONNECTED. - stopIpManager(); + stopIpClient(); - mIpManager.setHttpProxy(currentConfig.getHttpProxy()); + mIpClient.setHttpProxy(currentConfig.getHttpProxy()); if (!TextUtils.isEmpty(mTcpBufferSizes)) { - mIpManager.setTcpBufferSizes(mTcpBufferSizes); + mIpClient.setTcpBufferSizes(mTcpBufferSizes); } - final IpManager.ProvisioningConfiguration prov; + final IpClient.ProvisioningConfiguration prov; if (!isUsingStaticIp) { - prov = IpManager.buildProvisioningConfiguration() + prov = IpClient.buildProvisioningConfiguration() .withPreDhcpAction() .withApfCapabilities(mWifiNative.getApfCapabilities()) .withNetwork(getCurrentNetwork()) @@ -6091,14 +6091,14 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss .build(); } else { StaticIpConfiguration staticIpConfig = currentConfig.getStaticIpConfiguration(); - prov = IpManager.buildProvisioningConfiguration() + prov = IpClient.buildProvisioningConfiguration() .withStaticConfiguration(staticIpConfig) .withApfCapabilities(mWifiNative.getApfCapabilities()) .withNetwork(getCurrentNetwork()) .withDisplayName(currentConfig.SSID) .build(); } - mIpManager.startProvisioning(prov); + mIpClient.startProvisioning(prov); // Get Link layer stats so as we get fresh tx packet counters getWifiLinkLayerStats(); } @@ -6294,10 +6294,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss // We used to transition to ObtainingIpState in an // attempt to do DHCPv4 RENEWs on framework roams. // DHCP can take too long to time out, and we now rely - // upon IpManager's use of IpReachabilityMonitor to + // upon IpClient's use of IpReachabilityMonitor to // confirm our current network configuration. // - // mIpManager.confirmConfiguration() is called within + // mIpClient.confirmConfiguration() is called within // the handling of SupplicantState.COMPLETED. transitionTo(mConnectedState); } else { diff --git a/com/android/server/wifi/WifiVendorHal.java b/com/android/server/wifi/WifiVendorHal.java index e0467d78..cec36a56 100644 --- a/com/android/server/wifi/WifiVendorHal.java +++ b/com/android/server/wifi/WifiVendorHal.java @@ -238,11 +238,6 @@ public class WifiVendorHal { clearState(); } - // TODO: b/65014872 remove - have RttService (RTT2) interact directly with HalDeviceManager - public IWifiRttController getRttController() { - return mIWifiRttController; - } - private WifiNative.VendorHalDeathEventHandler mDeathEventHandler; /** @@ -312,7 +307,7 @@ public class WifiVendorHal { if (!registerStaIfaceCallback()) { return startFailedTo("register sta iface callback"); } - mIWifiRttController = mHalDeviceManager.createRttController(iface); + mIWifiRttController = mHalDeviceManager.createRttController(); if (mIWifiRttController == null) { return startFailedTo("create RTT controller"); } diff --git a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java index d70ce416..fa162531 100644 --- a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java +++ b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java @@ -32,7 +32,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkUtils; -import android.net.ip.IpManager; +import android.net.ip.IpClient; import android.net.wifi.WpsInfo; import android.net.wifi.p2p.IWifiP2pManager; import android.net.wifi.p2p.WifiP2pConfig; @@ -113,7 +113,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { private Context mContext; INetworkManagementService mNwService; - private IpManager mIpManager; + private IpClient mIpClient; private DhcpResults mDhcpResults; private P2pStateMachine mP2pStateMachine; @@ -180,12 +180,12 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { // msg.obj = StateMachine to send to when blocked public static final int BLOCK_DISCOVERY = BASE + 15; - // Messages for interaction with IpManager. - private static final int IPM_PRE_DHCP_ACTION = BASE + 30; - private static final int IPM_POST_DHCP_ACTION = BASE + 31; - private static final int IPM_DHCP_RESULTS = BASE + 32; - private static final int IPM_PROVISIONING_SUCCESS = BASE + 33; - private static final int IPM_PROVISIONING_FAILURE = BASE + 34; + // Messages for interaction with IpClient. + private static final int IPC_PRE_DHCP_ACTION = BASE + 30; + private static final int IPC_POST_DHCP_ACTION = BASE + 31; + private static final int IPC_DHCP_RESULTS = BASE + 32; + private static final int IPC_PROVISIONING_SUCCESS = BASE + 33; + private static final int IPC_PROVISIONING_FAILURE = BASE + 34; public static final int ENABLED = 1; public static final int DISABLED = 0; @@ -442,50 +442,50 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { } } - private void stopIpManager() { - if (mIpManager != null) { - mIpManager.stop(); - mIpManager = null; + private void stopIpClient() { + if (mIpClient != null) { + mIpClient.stop(); + mIpClient = null; } mDhcpResults = null; } - private void startIpManager(String ifname) { - stopIpManager(); + private void startIpClient(String ifname) { + stopIpClient(); - mIpManager = new IpManager(mContext, ifname, - new IpManager.Callback() { + mIpClient = new IpClient(mContext, ifname, + new IpClient.Callback() { @Override public void onPreDhcpAction() { - mP2pStateMachine.sendMessage(IPM_PRE_DHCP_ACTION); + mP2pStateMachine.sendMessage(IPC_PRE_DHCP_ACTION); } @Override public void onPostDhcpAction() { - mP2pStateMachine.sendMessage(IPM_POST_DHCP_ACTION); + mP2pStateMachine.sendMessage(IPC_POST_DHCP_ACTION); } @Override public void onNewDhcpResults(DhcpResults dhcpResults) { - mP2pStateMachine.sendMessage(IPM_DHCP_RESULTS, dhcpResults); + mP2pStateMachine.sendMessage(IPC_DHCP_RESULTS, dhcpResults); } @Override public void onProvisioningSuccess(LinkProperties newLp) { - mP2pStateMachine.sendMessage(IPM_PROVISIONING_SUCCESS); + mP2pStateMachine.sendMessage(IPC_PROVISIONING_SUCCESS); } @Override public void onProvisioningFailure(LinkProperties newLp) { - mP2pStateMachine.sendMessage(IPM_PROVISIONING_FAILURE); + mP2pStateMachine.sendMessage(IPC_PROVISIONING_FAILURE); } }, mNwService); - final IpManager.ProvisioningConfiguration config = - mIpManager.buildProvisioningConfiguration() - .withoutIPv6() - .withoutIpReachabilityMonitor() - .withPreDhcpAction(30 * 1000) - .withProvisioningTimeoutMs(36 * 1000) - .build(); - mIpManager.startProvisioning(config); + final IpClient.ProvisioningConfiguration config = + mIpClient.buildProvisioningConfiguration() + .withoutIPv6() + .withoutIpReachabilityMonitor() + .withPreDhcpAction(30 * 1000) + .withProvisioningTimeoutMs(36 * 1000) + .build(); + mIpClient.startProvisioning(config); } /** @@ -642,10 +642,10 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { pw.println("mDeathDataByBinder " + mDeathDataByBinder); pw.println(); - final IpManager ipManager = mIpManager; - if (ipManager != null) { - pw.println("mIpManager:"); - ipManager.dump(fd, pw, args); + final IpClient ipClient = mIpClient; + if (ipClient != null) { + pw.println("mIpClient:"); + ipClient.dump(fd, pw, args); } } @@ -945,11 +945,11 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { case DROP_WIFI_USER_REJECT: case GROUP_CREATING_TIMED_OUT: case DISABLE_P2P_TIMED_OUT: - case IPM_PRE_DHCP_ACTION: - case IPM_POST_DHCP_ACTION: - case IPM_DHCP_RESULTS: - case IPM_PROVISIONING_SUCCESS: - case IPM_PROVISIONING_FAILURE: + case IPC_PRE_DHCP_ACTION: + case IPC_POST_DHCP_ACTION: + case IPC_DHCP_RESULTS: + case IPC_PROVISIONING_SUCCESS: + case IPC_PROVISIONING_FAILURE: case WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT: case SET_MIRACAST_MODE: case WifiP2pManager.START_LISTEN: @@ -1958,7 +1958,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { startDhcpServer(mGroup.getInterface()); } else { mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S); - startIpManager(mGroup.getInterface()); + startIpClient(mGroup.getInterface()); WifiP2pDevice groupOwner = mGroup.getOwner(); WifiP2pDevice peer = mPeers.get(groupOwner.deviceAddress); if (peer != null) { @@ -2218,17 +2218,17 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { loge("Disconnect on unknown device: " + device); } break; - case IPM_PRE_DHCP_ACTION: + case IPC_PRE_DHCP_ACTION: mWifiNative.setP2pPowerSave(mGroup.getInterface(), false); - mIpManager.completedPreDhcpAction(); + mIpClient.completedPreDhcpAction(); break; - case IPM_POST_DHCP_ACTION: + case IPC_POST_DHCP_ACTION: mWifiNative.setP2pPowerSave(mGroup.getInterface(), true); break; - case IPM_DHCP_RESULTS: + case IPC_DHCP_RESULTS: mDhcpResults = (DhcpResults) message.obj; break; - case IPM_PROVISIONING_SUCCESS: + case IPC_PROVISIONING_SUCCESS: if (DBG) logd("mDhcpResults: " + mDhcpResults); if (mDhcpResults != null) { setWifiP2pInfoOnGroupFormation(mDhcpResults.serverAddress); @@ -2244,7 +2244,7 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { loge("Failed to add iface to local network " + e); } break; - case IPM_PROVISIONING_FAILURE: + case IPC_PROVISIONING_FAILURE: loge("IP provisioning failed"); mWifiNative.p2pGroupRemove(mGroup.getInterface()); break; @@ -3061,8 +3061,8 @@ public class WifiP2pServiceImpl extends IWifiP2pManager.Stub { if (mGroup.isGroupOwner()) { stopDhcpServer(mGroup.getInterface()); } else { - if (DBG) logd("stop IpManager"); - stopIpManager(); + if (DBG) logd("stop IpClient"); + stopIpClient(); try { mNwService.removeInterfaceFromLocalNetwork(mGroup.getInterface()); } catch (RemoteException e) { diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java index 21aa1ce4..f731c195 100644 --- a/com/android/server/wifi/rtt/RttNative.java +++ b/com/android/server/wifi/rtt/RttNative.java @@ -34,8 +34,6 @@ import android.os.RemoteException; import android.util.Log; import com.android.server.wifi.HalDeviceManager; -import com.android.server.wifi.WifiNative; -import com.android.server.wifi.WifiVendorHal; import com.android.server.wifi.util.NativeUtil; import java.io.FileDescriptor; @@ -52,15 +50,55 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub { private final RttServiceImpl mRttService; private final HalDeviceManager mHalDeviceManager; - private final WifiVendorHal mWifiVendorHal; - private boolean mIsInitialized = false; + private Object mLock = new Object(); - public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager, - WifiNative wifiNative) { + private IWifiRttController mIWifiRttController; + + public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager) { mRttService = rttService; mHalDeviceManager = halDeviceManager; - mWifiVendorHal = wifiNative.getVendorHal(); + } + + /** + * Initialize the object - registering with the HAL device manager. + */ + public void start() { + synchronized (mLock) { + mHalDeviceManager.initialize(); + mHalDeviceManager.registerStatusListener(() -> { + if (VDBG) Log.d(TAG, "hdm.onStatusChanged"); + updateController(); + }, null); + updateController(); + } + } + + private void updateController() { + if (VDBG) Log.v(TAG, "updateController: mIWifiRttController=" + mIWifiRttController); + + // only care about isStarted (Wi-Fi started) not isReady - since if not + // ready then Wi-Fi will also be down. + synchronized (mLock) { + if (mHalDeviceManager.isStarted()) { + if (mIWifiRttController == null) { + mIWifiRttController = mHalDeviceManager.createRttController(); + if (mIWifiRttController == null) { + Log.e(TAG, "updateController: Failed creating RTT controller - but Wifi is " + + "started!"); + } else { + try { + mIWifiRttController.registerEventCallback(this); + } catch (RemoteException e) { + Log.e(TAG, "updateController: exception registering callback: " + e); + mIWifiRttController = null; + } + } + } + } else { + mIWifiRttController = null; + } + } } /** @@ -73,45 +111,62 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub { */ public boolean rangeRequest(int cmdId, RangingRequest request) { if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request); - // TODO: b/65014872 replace by direct access to HalDeviceManager - IWifiRttController rttController = mWifiVendorHal.getRttController(); - if (rttController == null) { - Log.e(TAG, "rangeRequest: RttController is null"); - return false; - } - if (!mIsInitialized) { + synchronized (mLock) { + if (mIWifiRttController == null) { + Log.e(TAG, "rangeRequest: RttController is null"); + return false; + } + + ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request); + if (rttConfig == null) { + Log.e(TAG, "rangeRequest: invalid request parameters"); + return false; + } + try { - WifiStatus status = rttController.registerEventCallback(this); + WifiStatus status = mIWifiRttController.rangeRequest(cmdId, rttConfig); if (status.code != WifiStatusCode.SUCCESS) { - Log.e(TAG, - "rangeRequest: cannot register event callback -- code=" + status.code); + Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code); return false; } } catch (RemoteException e) { - Log.e(TAG, "rangeRequest: exception registering callback: " + e); + Log.e(TAG, "rangeRequest: exception issuing range request: " + e); return false; } - mIsInitialized = true; - } - ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request); - if (rttConfig == null) { - Log.e(TAG, "rangeRequest: invalid request parameters"); - return false; + return true; } + } - try { - WifiStatus status = rttController.rangeRequest(cmdId, rttConfig); - if (status.code != WifiStatusCode.SUCCESS) { - Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code); + /** + * Cancel an outstanding ranging request: no guarantees of execution - we will ignore any + * results which are returned for the canceled request. + * + * @param cmdId The cmdId issued with the original rangeRequest command. + * @param macAddresses A list of MAC addresses for which to cancel the operation. + * @return Success status: true for success, false for failure. + */ + public boolean rangeCancel(int cmdId, ArrayList<byte[]> macAddresses) { + if (VDBG) Log.v(TAG, "rangeCancel: cmdId=" + cmdId); + synchronized (mLock) { + if (mIWifiRttController == null) { + Log.e(TAG, "rangeCancel: RttController is null"); return false; } - } catch (RemoteException e) { - Log.e(TAG, "rangeRequest: exception issuing range request: " + e); - return false; - } - return true; + try { + WifiStatus status = mIWifiRttController.rangeCancel(cmdId, macAddresses); + if (status.code != WifiStatusCode.SUCCESS) { + Log.e(TAG, "rangeCancel: cannot issue range cancel -- code=" + status.code); + return false; + } + } catch (RemoteException e) { + Log.e(TAG, "rangeCancel: exception issuing range cancel: " + e); + return false; + } + + return true; + } } /** @@ -259,6 +314,7 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub { */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("RttNative:"); - pw.println(" mIsInitialized: " + mIsInitialized); + pw.println(" mHalDeviceManager: " + mHalDeviceManager); + pw.println(" mIWifiRttController: " + mIWifiRttController); } } diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java index f248f725..35f76155 100644 --- a/com/android/server/wifi/rtt/RttService.java +++ b/com/android/server/wifi/rtt/RttService.java @@ -25,7 +25,6 @@ import android.util.Log; import com.android.server.SystemService; import com.android.server.wifi.HalDeviceManager; import com.android.server.wifi.WifiInjector; -import com.android.server.wifi.WifiNative; import com.android.server.wifi.util.WifiPermissionsUtil; /** @@ -61,13 +60,13 @@ public class RttService extends SystemService { HalDeviceManager halDeviceManager = wifiInjector.getHalDeviceManager(); HandlerThread handlerThread = wifiInjector.getRttHandlerThread(); - WifiNative wifiNative = wifiInjector.getWifiNative(); WifiPermissionsUtil wifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil(); IWifiAwareManager awareBinder = (IWifiAwareManager) ServiceManager.getService( Context.WIFI_AWARE_SERVICE); - RttNative rttNative = new RttNative(mImpl, halDeviceManager, wifiNative); + RttNative rttNative = new RttNative(mImpl, halDeviceManager); + rttNative.start(); mImpl.start(handlerThread.getLooper(), awareBinder, rttNative, wifiPermissionsUtil); } } diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java index 9a0a1af0..19210479 100644 --- a/com/android/server/wifi/rtt/RttServiceImpl.java +++ b/com/android/server/wifi/rtt/RttServiceImpl.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.hardware.wifi.V1_0.RttResult; import android.hardware.wifi.V1_0.RttStatus; +import android.net.wifi.ScanResult; import android.net.wifi.aware.IWifiAwareMacAddressProvider; import android.net.wifi.aware.IWifiAwareManager; import android.net.wifi.aware.PeerHandle; @@ -33,8 +34,11 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.WakeupMessage; import com.android.server.wifi.util.NativeUtil; import com.android.server.wifi.util.WifiPermissionsUtil; @@ -63,6 +67,10 @@ public class RttServiceImpl extends IWifiRttManager.Stub { private RttServiceSynchronized mRttServiceSynchronized; + @VisibleForTesting + public static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout"; + + private static final long HAL_RANGING_TIMEOUT_MS = 5_000; public RttServiceImpl(Context context) { mContext = context; @@ -204,11 +212,41 @@ public class RttServiceImpl extends IWifiRttManager.Stub { private RttNative mRttNative; private int mNextCommandId = 1000; private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>(); + private WakeupMessage mRangingTimeoutMessage = null; RttServiceSynchronized(Looper looper, RttNative rttNative) { mRttNative = rttNative; mHandler = new Handler(looper); + mRangingTimeoutMessage = new WakeupMessage(mContext, mHandler, + HAL_RANGING_TIMEOUT_TAG, () -> { + timeoutRangingRequest(); + }); + } + + private void cancelRanging(RttRequestInfo rri) { + ArrayList<byte[]> macAddresses = new ArrayList<>(); + for (RangingRequest.RttPeer peer : rri.request.mRttPeers) { + if (peer instanceof RangingRequest.RttPeerAp) { + ScanResult scanResult = + ((RangingRequest.RttPeerAp) peer).scanResult; + + byte[] addr = NativeUtil.macAddressToByteArray(scanResult.BSSID); + if (addr.length != 6) { + Log.e(TAG, "Invalid configuration: unexpected BSSID length -- " + + peer); + continue; + } + macAddresses.add(addr); + } else if (peer instanceof RangingRequest.RttPeerAware) { + if (((RangingRequest.RttPeerAware) peer).peerMacAddress != null) { + macAddresses.add( + ((RangingRequest.RttPeerAware) peer).peerMacAddress); + } + } + } + + mRttNative.rangeCancel(rri.cmdId, macAddresses); } private void cleanUpOnClientDeath(int uid) { @@ -220,12 +258,13 @@ public class RttServiceImpl extends IWifiRttManager.Stub { while (it.hasNext()) { RttRequestInfo rri = it.next(); if (rri.uid == uid) { - // TODO: actually abort operation - though API is not clear or clean - if (rri.cmdId == 0) { - // Until that happens we will get results for the last operation: which is - // why we don't dispatch a new range request off the queue and keep the - // currently running operation in the queue + if (!rri.dispatchedToNative) { it.remove(); + } else { + Log.d(TAG, "Client death - cancelling RTT operation in progress: cmdId=" + + rri.cmdId); + mRangingTimeoutMessage.cancel(); + cancelRanging(rri); } } } @@ -236,6 +275,30 @@ public class RttServiceImpl extends IWifiRttManager.Stub { } } + private void timeoutRangingRequest() { + if (VDBG) { + Log.v(TAG, "RttServiceSynchronized.timeoutRangingRequest mRttRequestQueue=" + + mRttRequestQueue); + } + if (mRttRequestQueue.size() == 0) { + Log.w(TAG, "RttServiceSynchronized.timeoutRangingRequest: but nothing in queue!?"); + return; + } + RttRequestInfo rri = mRttRequestQueue.get(0); + if (!rri.dispatchedToNative) { + Log.w(TAG, "RttServiceSynchronized.timeoutRangingRequest: command not dispatched " + + "to native!?"); + return; + } + cancelRanging(rri); + try { + rri.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL); + } catch (RemoteException e) { + Log.e(TAG, "RttServiceSynchronized.timeoutRangingRequest: callback failed: " + e); + } + executeNextRangingRequestIfPossible(true); + } + private void queueRangingRequest(int uid, IBinder binder, IBinder.DeathRecipient dr, String callingPackage, RangingRequest request, IRttCallback callback) { RttRequestInfo newRequest = new RttRequestInfo(); @@ -272,7 +335,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub { } RttRequestInfo nextRequest = mRttRequestQueue.get(0); - if (nextRequest.cmdId != 0) { + if (nextRequest.peerHandlesTranslated || nextRequest.dispatchedToNative) { if (VDBG) { Log.v(TAG, "executeNextRangingRequestIfPossible: called but a command is " + "executing. topOfQueue=" + nextRequest); @@ -280,7 +343,6 @@ public class RttServiceImpl extends IWifiRttManager.Stub { return; } - nextRequest.cmdId = mNextCommandId++; startRanging(nextRequest); } @@ -297,7 +359,11 @@ public class RttServiceImpl extends IWifiRttManager.Stub { return; } - if (!mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) { + nextRequest.cmdId = mNextCommandId++; + if (mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) { + mRangingTimeoutMessage.schedule( + SystemClock.elapsedRealtime() + HAL_RANGING_TIMEOUT_MS); + } else { Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed"); try { nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL); @@ -307,6 +373,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub { } executeNextRangingRequestIfPossible(true); } + nextRequest.dispatchedToNative = true; } /** @@ -402,6 +469,7 @@ public class RttServiceImpl extends IWifiRttManager.Stub { + "pending!?"); return; } + mRangingTimeoutMessage.cancel(); RttRequestInfo topOfQueueRequest = mRttRequestQueue.get(0); if (VDBG) { @@ -520,10 +588,10 @@ public class RttServiceImpl extends IWifiRttManager.Stub { public IBinder.DeathRecipient dr; public String callingPackage; public RangingRequest request; - public byte[] mac; public IRttCallback callback; public int cmdId = 0; // uninitialized cmdId value + public boolean dispatchedToNative = false; public boolean peerHandlesTranslated = false; @Override diff --git a/com/android/server/wifi/util/WifiPermissionsUtil.java b/com/android/server/wifi/util/WifiPermissionsUtil.java index 069e5a82..d3c072fb 100644 --- a/com/android/server/wifi/util/WifiPermissionsUtil.java +++ b/com/android/server/wifi/util/WifiPermissionsUtil.java @@ -21,7 +21,6 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; -import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.os.Binder; import android.os.RemoteException; @@ -93,16 +92,6 @@ public class WifiPermissionsUtil { } /** - * Check and enforce tether change permission. - * - * @param context Context object of the caller. - */ - public void enforceTetherChangePermission(Context context) { - String pkgName = context.getOpPackageName(); - ConnectivityManager.enforceTetherChangePermission(context, pkgName); - } - - /** * Check and enforce Location permission. * * @param pkgName PackageName of the application requesting access diff --git a/com/android/server/wm/AccessibilityController.java b/com/android/server/wm/AccessibilityController.java index 6484a13d..de4fd7cd 100644 --- a/com/android/server/wm/AccessibilityController.java +++ b/com/android/server/wm/AccessibilityController.java @@ -74,12 +74,12 @@ import java.util.Set; */ final class AccessibilityController { - private final WindowManagerService mWindowManagerService; + private final WindowManagerService mService; private static final float[] sTempFloats = new float[9]; public AccessibilityController(WindowManagerService service) { - mWindowManagerService = service; + mService = service; } private DisplayMagnifier mDisplayMagnifier; @@ -91,7 +91,7 @@ final class AccessibilityController { if (mDisplayMagnifier != null) { throw new IllegalStateException("Magnification callbacks already set!"); } - mDisplayMagnifier = new DisplayMagnifier(mWindowManagerService, callbacks); + mDisplayMagnifier = new DisplayMagnifier(mService, callbacks); } else { if (mDisplayMagnifier == null) { throw new IllegalStateException("Magnification callbacks already cleared!"); @@ -108,7 +108,7 @@ final class AccessibilityController { "Windows for accessibility callback already set!"); } mWindowsForAccessibilityObserver = new WindowsForAccessibilityObserver( - mWindowManagerService, callback); + mService, callback); } else { if (mWindowsForAccessibilityObserver == null) { throw new IllegalStateException( @@ -120,7 +120,7 @@ final class AccessibilityController { public void performComputeChangedWindowsNotLocked() { WindowsForAccessibilityObserver observer = null; - synchronized (mWindowManagerService) { + synchronized (mService) { observer = mWindowsForAccessibilityObserver; } if (observer != null) { @@ -188,7 +188,7 @@ final class AccessibilityController { // Not relevant for the display magnifier. WindowsForAccessibilityObserver observer = null; - synchronized (mWindowManagerService) { + synchronized (mService) { observer = mWindowsForAccessibilityObserver; } if (observer != null) { @@ -268,7 +268,7 @@ final class AccessibilityController { private final Region mTempRegion4 = new Region(); private final Context mContext; - private final WindowManagerService mWindowManagerService; + private final WindowManagerService mService; private final MagnifiedViewport mMagnifedViewport; private final Handler mHandler; @@ -281,9 +281,9 @@ final class AccessibilityController { public DisplayMagnifier(WindowManagerService windowManagerService, MagnificationCallbacks callbacks) { mContext = windowManagerService.mContext; - mWindowManagerService = windowManagerService; + mService = windowManagerService; mCallbacks = callbacks; - mHandler = new MyHandler(mWindowManagerService.mH.getLooper()); + mHandler = new MyHandler(mService.mH.getLooper()); mMagnifedViewport = new MagnifiedViewport(); mLongAnimationDuration = mContext.getResources().getInteger( com.android.internal.R.integer.config_longAnimTime); @@ -292,7 +292,7 @@ final class AccessibilityController { public void setMagnificationSpecLocked(MagnificationSpec spec) { mMagnifedViewport.updateMagnificationSpecLocked(spec); mMagnifedViewport.recomputeBoundsLocked(); - mWindowManagerService.scheduleAnimationLocked(); + mService.scheduleAnimationLocked(); } public void setForceShowMagnifiableBoundsLocked(boolean show) { @@ -330,7 +330,7 @@ final class AccessibilityController { Slog.i(LOG_TAG, "Layers changed."); } mMagnifedViewport.recomputeBoundsLocked(); - mWindowManagerService.scheduleAnimationLocked(); + mService.scheduleAnimationLocked(); } public void onRotationChangedLocked(DisplayContent displayContent) { @@ -421,7 +421,7 @@ final class AccessibilityController { public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) { MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked(); if (spec != null && !spec.isNop()) { - if (!mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { + if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { return null; } } @@ -565,7 +565,7 @@ final class AccessibilityController { portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION); windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE); - if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { + if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) { mMagnificationRegion.op(windowBounds, Region.Op.UNION); mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT); } else { @@ -632,7 +632,7 @@ final class AccessibilityController { if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) { setMagnifiedRegionBorderShownLocked(false, false); final long delay = (long) (mLongAnimationDuration - * mWindowManagerService.getWindowAnimationScaleLocked()); + * mService.getWindowAnimationScaleLocked()); Message message = mHandler.obtainMessage( MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED); mHandler.sendMessageDelayed(message, delay); @@ -675,7 +675,7 @@ final class AccessibilityController { } private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) { - final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked(); + final DisplayContent dc = mService.getDefaultDisplayContentLocked(); dc.forAllWindows((w) -> { if (w.isOnScreen() && w.isVisibleLw() && !w.mWinAnimator.mEnterAnimationPending) { @@ -705,23 +705,25 @@ final class AccessibilityController { SurfaceControl surfaceControl = null; try { mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); - surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession, - SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, - SurfaceControl.HIDDEN); + surfaceControl = new SurfaceControl.Builder(mService.mFxSession) + .setName(SURFACE_TITLE) + .setSize(mTempPoint.x, mTempPoint.y) // not a typo + .setFormat(PixelFormat.TRANSLUCENT) + .build(); } catch (OutOfResourcesException oore) { /* ignore */ } mSurfaceControl = surfaceControl; mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay() .getLayerStack()); - mSurfaceControl.setLayer(mWindowManagerService.mPolicy.getWindowLayerFromTypeLw( + mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw( TYPE_MAGNIFICATION_OVERLAY) * WindowManagerService.TYPE_LAYER_MULTIPLIER); mSurfaceControl.setPosition(0, 0); mSurface.copyFrom(mSurfaceControl); mAnimationController = new AnimationController(context, - mWindowManagerService.mH.getLooper()); + mService.mH.getLooper()); TypedValue typedValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight, @@ -736,7 +738,7 @@ final class AccessibilityController { } public void setShown(boolean shown, boolean animate) { - synchronized (mWindowManagerService.mWindowMap) { + synchronized (mService.mWindowMap) { if (mShown == shown) { return; } @@ -751,13 +753,13 @@ final class AccessibilityController { @SuppressWarnings("unused") // Called reflectively from an animator. public int getAlpha() { - synchronized (mWindowManagerService.mWindowMap) { + synchronized (mService.mWindowMap) { return mAlpha; } } public void setAlpha(int alpha) { - synchronized (mWindowManagerService.mWindowMap) { + synchronized (mService.mWindowMap) { if (mAlpha == alpha) { return; } @@ -770,7 +772,7 @@ final class AccessibilityController { } public void setBounds(Region bounds) { - synchronized (mWindowManagerService.mWindowMap) { + synchronized (mService.mWindowMap) { if (mBounds.equals(bounds)) { return; } @@ -783,7 +785,7 @@ final class AccessibilityController { } public void updateSize() { - synchronized (mWindowManagerService.mWindowMap) { + synchronized (mService.mWindowMap) { mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y); invalidate(mDirtyRect); @@ -797,12 +799,12 @@ final class AccessibilityController { mDirtyRect.setEmpty(); } mInvalidated = true; - mWindowManagerService.scheduleAnimationLocked(); + mService.scheduleAnimationLocked(); } /** NOTE: This has to be called within a surface transaction. */ public void drawIfNeeded() { - synchronized (mWindowManagerService.mWindowMap) { + synchronized (mService.mWindowMap) { if (!mInvalidated) { return; } @@ -950,11 +952,11 @@ final class AccessibilityController { } break; case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : { - synchronized (mWindowManagerService.mWindowMap) { + synchronized (mService.mWindowMap) { if (mMagnifedViewport.isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) { mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true); - mWindowManagerService.scheduleAnimationLocked(); + mService.scheduleAnimationLocked(); } } } break; @@ -995,7 +997,7 @@ final class AccessibilityController { private final Context mContext; - private final WindowManagerService mWindowManagerService; + private final WindowManagerService mService; private final Handler mHandler; @@ -1006,9 +1008,9 @@ final class AccessibilityController { public WindowsForAccessibilityObserver(WindowManagerService windowManagerService, WindowsForAccessibilityCallback callback) { mContext = windowManagerService.mContext; - mWindowManagerService = windowManagerService; + mService = windowManagerService; mCallback = callback; - mHandler = new MyHandler(mWindowManagerService.mH.getLooper()); + mHandler = new MyHandler(mService.mH.getLooper()); mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration .getSendRecurringAccessibilityEventsInterval(); computeChangedWindows(); @@ -1034,11 +1036,11 @@ final class AccessibilityController { boolean windowsChanged = false; List<WindowInfo> windows = new ArrayList<WindowInfo>(); - synchronized (mWindowManagerService.mWindowMap) { + synchronized (mService.mWindowMap) { // Do not send the windows if there is no current focus as // the window manager is still looking for where to put it. // We will do the work when we get a focus change callback. - if (mWindowManagerService.mCurrentFocus == null) { + if (mService.mCurrentFocus == null) { return; } @@ -1320,7 +1322,7 @@ final class AccessibilityController { } private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) { - final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked(); + final DisplayContent dc = mService.getDefaultDisplayContentLocked(); dc.forAllWindows((w) -> { if (w.isVisibleLw()) { outWindows.put(w.mLayer, w); diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java index 5d034935..e873d32e 100644 --- a/com/android/server/wm/AppWindowToken.java +++ b/com/android/server/wm/AppWindowToken.java @@ -1621,10 +1621,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @CallSuper @Override - public void writeToProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { final long token = proto.start(fieldId); writeNameToProto(proto, NAME); - super.writeToProto(proto, WINDOW_TOKEN); + super.writeToProto(proto, WINDOW_TOKEN, trim); proto.end(token); } diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java index d206554c..9729e501 100644 --- a/com/android/server/wm/BlackFrame.java +++ b/com/android/server/wm/BlackFrame.java @@ -50,8 +50,11 @@ public class BlackFrame { int w = r-l; int h = b-t; - surface = new SurfaceControl(session, "BlackSurface", - w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN); + surface = new SurfaceControl.Builder(session) + .setName("BlackSurface") + .setSize(w, h) + .setColorLayer(true) + .build(); surface.setAlpha(1); surface.setLayerStack(layerStack); diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java index 85f468b5..2d5d1b2f 100644 --- a/com/android/server/wm/CircularDisplayMask.java +++ b/com/android/server/wm/CircularDisplayMask.java @@ -66,8 +66,11 @@ class CircularDisplayMask { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x, - mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + ctrl = new SurfaceControl.Builder(session) + .setName("CircularDisplayMask") + .setSize(mScreenSize.x, mScreenSize.y) // not a typo + .setFormat(PixelFormat.TRANSLUCENT) + .build(); ctrl.setLayerStack(display.getLayerStack()); ctrl.setLayer(zOrder); diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java index a4ab3ba3..cc948070 100644 --- a/com/android/server/wm/ConfigurationContainer.java +++ b/com/android/server/wm/ConfigurationContainer.java @@ -50,6 +50,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { /** Contains override configuration settings applied to this configuration container. */ private Configuration mOverrideConfiguration = new Configuration(); + /** True if mOverrideConfiguration is not empty */ + private boolean mHasOverrideConfiguration; + /** * Contains full configuration applied to this configuration container. Corresponds to full * parent's config with applied {@link #mOverrideConfiguration}. @@ -101,6 +104,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { * @see #mFullConfiguration */ public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { + // Pre-compute this here, so we don't need to go through the entire Configuration when + // writing to proto (which has significant cost if we write a lot of empty configurations). + mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration); mOverrideConfiguration.setTo(overrideConfiguration); // Update full configuration of this container and all its children. final ConfigurationContainer parent = getParent(); @@ -330,18 +336,23 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { * Write to a protocol buffer output stream. Protocol buffer message definition is at * {@link com.android.server.wm.proto.ConfigurationContainerProto}. * - * @param protoOutputStream Stream to write the ConfigurationContainer object to. - * @param fieldId Field Id of the ConfigurationContainer as defined in the parent - * message. + * @param proto Stream to write the ConfigurationContainer object to. + * @param fieldId Field Id of the ConfigurationContainer as defined in the parent + * message. + * @param trim If true, reduce amount of data written. * @hide */ @CallSuper - public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { - final long token = protoOutputStream.start(fieldId); - mOverrideConfiguration.writeToProto(protoOutputStream, OVERRIDE_CONFIGURATION); - mFullConfiguration.writeToProto(protoOutputStream, FULL_CONFIGURATION); - mMergedOverrideConfiguration.writeToProto(protoOutputStream, MERGED_OVERRIDE_CONFIGURATION); - protoOutputStream.end(token); + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { + final long token = proto.start(fieldId); + if (!trim || mHasOverrideConfiguration) { + mOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION); + } + if (!trim) { + mFullConfiguration.writeToProto(proto, FULL_CONFIGURATION); + mMergedOverrideConfiguration.writeToProto(proto, MERGED_OVERRIDE_CONFIGURATION); + } + proto.end(token); } /** diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java index 48181d30..401547e6 100644 --- a/com/android/server/wm/DimLayer.java +++ b/com/android/server/wm/DimLayer.java @@ -104,9 +104,11 @@ public class DimLayer { private void constructSurface(WindowManagerService service) { service.openSurfaceTransaction(); try { - mDimSurface = new SurfaceControl(service.mFxSession, mName, - 16, 16, PixelFormat.OPAQUE, - SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN); + 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"); diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java index 03fdc968..4c6ab3f7 100644 --- a/com/android/server/wm/DisplayContent.java +++ b/com/android/server/wm/DisplayContent.java @@ -2137,27 +2137,27 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @CallSuper @Override - public void writeToProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { final long token = proto.start(fieldId); - super.writeToProto(proto, WINDOW_CONTAINER); + super.writeToProto(proto, WINDOW_CONTAINER, trim); proto.write(ID, mDisplayId); for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx); - stack.writeToProto(proto, STACKS); + stack.writeToProto(proto, STACKS, trim); } mDividerControllerLocked.writeToProto(proto, DOCKED_STACK_DIVIDER_CONTROLLER); mPinnedStackControllerLocked.writeToProto(proto, PINNED_STACK_CONTROLLER); for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) { final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i); - windowToken.writeToProto(proto, ABOVE_APP_WINDOWS); + windowToken.writeToProto(proto, ABOVE_APP_WINDOWS, trim); } for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) { final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i); - windowToken.writeToProto(proto, BELOW_APP_WINDOWS); + windowToken.writeToProto(proto, BELOW_APP_WINDOWS, trim); } for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) { final WindowToken windowToken = mImeWindowsContainers.getChildAt(i); - windowToken.writeToProto(proto, IME_WINDOWS); + windowToken.writeToProto(proto, IME_WINDOWS, trim); } proto.write(DPI, mBaseDisplayDensity); mDisplayInfo.writeToProto(proto, DISPLAY_INFO); diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java index 52526e2f..5ce4d46c 100644 --- a/com/android/server/wm/DockedStackDividerController.java +++ b/com/android/server/wm/DockedStackDividerController.java @@ -137,6 +137,8 @@ public class DockedStackDividerController implements DimLayerUser { float mLastDividerProgress; private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4]; private boolean mImeHideRequested; + private final Rect mLastDimLayerRect = new Rect(); + private float mLastDimLayerAlpha; DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) { mService = service; @@ -525,7 +527,6 @@ public class DockedStackDividerController implements DimLayerUser { * display in that windowing mode. */ void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) { - mService.openSurfaceTransaction(); // TODO: Maybe only allow split-screen windowing modes? final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED ? mDisplayContent.getStack(targetWindowingMode) @@ -535,16 +536,33 @@ public class DockedStackDividerController implements DimLayerUser { if (visibleAndValid) { stack.getDimBounds(mTmpRect); if (mTmpRect.height() > 0 && mTmpRect.width() > 0) { - mDimLayer.setBounds(mTmpRect); - mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */); + 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(); + } + } + mLastDimLayerRect.set(mTmpRect); + mLastDimLayerAlpha = alpha; } else { visibleAndValid = false; } } if (!visibleAndValid) { - mDimLayer.hide(); + if (mLastDimLayerAlpha != 0f) { + try { + mService.openSurfaceTransaction(); + mDimLayer.hide(); + } finally { + mService.closeSurfaceTransaction(); + } + } + mLastDimLayerAlpha = 0f; } - mService.closeSurfaceTransaction(); } /** diff --git a/com/android/server/wm/DragDropController.java b/com/android/server/wm/DragDropController.java new file mode 100644 index 00000000..a3c61670 --- /dev/null +++ b/com/android/server/wm/DragDropController.java @@ -0,0 +1,301 @@ +/* + * 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 com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; +import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; +import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.content.ClipData; +import android.graphics.PixelFormat; +import android.os.Binder; +import android.os.IBinder; +import android.os.Message; +import android.util.Slog; +import android.view.Display; +import android.view.IWindow; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.view.View; +import com.android.server.wm.WindowManagerService.H; + +/** + * Managing drag and drop operations initiated by View#startDragAndDrop. + */ +class DragDropController { + private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; + private static final long DRAG_TIMEOUT_MS = 5000; + DragState mDragState; + + boolean dragDropActiveLocked() { + return mDragState != null; + } + + IBinder prepareDrag(WindowManagerService service, SurfaceSession session, int callerPid, + int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height + + " flags=" + Integer.toHexString(flags) + " win=" + window + + " asbinder=" + window.asBinder()); + } + + IBinder token = null; + + synchronized (service.mWindowMap) { + if (dragDropActiveLocked()) { + Slog.w(TAG_WM, "Drag already in progress"); + return null; + } + + // TODO(multi-display): support other displays + final DisplayContent displayContent = + service.getDefaultDisplayContentLocked(); + final Display display = displayContent.getDisplay(); + + final SurfaceControl surface = new SurfaceControl.Builder(session) + .setName("drag surface") + .setSize(width, height) + .setFormat(PixelFormat.TRANSLUCENT) + .build(); + surface.setLayerStack(display.getLayerStack()); + float alpha = 1; + if ((flags & View.DRAG_FLAG_OPAQUE) == 0) { + alpha = DRAG_SHADOW_ALPHA_TRANSPARENT; + } + surface.setAlpha(alpha); + + if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " + surface + ": CREATE"); + outSurface.copyFrom(surface); + final IBinder winBinder = window.asBinder(); + token = new Binder(); + mDragState = new DragState(service, token, surface, flags, winBinder); + mDragState.mPid = callerPid; + mDragState.mUid = callerUid; + mDragState.mOriginalAlpha = alpha; + token = mDragState.mToken = new Binder(); + + // 5 second timeout for this window to actually begin the drag + service.mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder); + Message msg = service.mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder); + service.mH.sendMessageDelayed(msg, DRAG_TIMEOUT_MS); + } + + return token; + } + + boolean performDrag(WindowManagerService service, IWindow window, IBinder dragToken, + int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, + ClipData data) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data); + } + + synchronized (service.mWindowMap) { + if (mDragState == null) { + Slog.w(TAG_WM, "No drag prepared"); + throw new IllegalStateException("performDrag() without prepareDrag()"); + } + + if (dragToken != mDragState.mToken) { + Slog.w(TAG_WM, "Performing mismatched drag"); + throw new IllegalStateException("performDrag() does not match prepareDrag()"); + } + + final WindowState callingWin = service.windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(TAG_WM, "Bad requesting window " + window); + return false; // !!! TODO: throw here? + } + + // !!! TODO: if input is not still focused on the initiating window, fail + // the drag initiation (e.g. an alarm window popped up just as the application + // called performDrag() + + service.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder()); + + // !!! TODO: extract the current touch (x, y) in screen coordinates. That + // will let us eliminate the (touchX,touchY) parameters from the API. + + // !!! FIXME: put all this heavy stuff onto the mH looper, as well as + // the actual drag event dispatch stuff in the dragstate + + final DisplayContent displayContent = callingWin.getDisplayContent(); + if (displayContent == null) { + return false; + } + Display display = displayContent.getDisplay(); + mDragState.register(display); + if (!service.mInputManager.transferTouchFocus(callingWin.mInputChannel, + mDragState.getInputChannel())) { + Slog.e(TAG_WM, "Unable to transfer touch focus"); + mDragState.unregister(); + mDragState.reset(); + mDragState = null; + return false; + } + + mDragState.mDisplayContent = displayContent; + mDragState.mData = data; + mDragState.broadcastDragStartedLw(touchX, touchY); + mDragState.overridePointerIconLw(touchSource); + + // remember the thumb offsets for later + mDragState.mThumbOffsetX = thumbCenterX; + mDragState.mThumbOffsetY = thumbCenterY; + + // Make the surface visible at the proper location + final SurfaceControl surfaceControl = mDragState.mSurfaceControl; + if (SHOW_LIGHT_TRANSACTIONS) Slog.i( + TAG_WM, ">>> OPEN TRANSACTION performDrag"); + service.openSurfaceTransaction(); + try { + surfaceControl.setPosition(touchX - thumbCenterX, + touchY - thumbCenterY); + surfaceControl.setLayer(mDragState.getDragLayerLw()); + surfaceControl.setLayerStack(display.getLayerStack()); + surfaceControl.show(); + } finally { + service.closeSurfaceTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i( + TAG_WM, "<<< CLOSE TRANSACTION performDrag"); + } + + mDragState.notifyLocationLw(touchX, touchY); + } + + return true; // success! + } + + void reportDropResult(WindowManagerService service, IWindow window, boolean consumed) { + IBinder token = window.asBinder(); + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); + } + + synchronized (service.mWindowMap) { + if (mDragState == null) { + // Most likely the drop recipient ANRed and we ended the drag + // out from under it. Log the issue and move on. + Slog.w(TAG_WM, "Drop result given but no drag in progress"); + return; + } + + if (mDragState.mToken != token) { + // We're in a drag, but the wrong window has responded. + Slog.w(TAG_WM, "Invalid drop-result claim by " + window); + throw new IllegalStateException("reportDropResult() by non-recipient"); + } + + // The right window has responded, even if it's no longer around, + // so be sure to halt the timeout even if the later WindowState + // lookup fails. + service.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder()); + WindowState callingWin = service.windowForClientLocked(null, window, false); + if (callingWin == null) { + Slog.w(TAG_WM, "Bad result-reporting window " + window); + return; // !!! TODO: throw here? + } + + mDragState.mDragResult = consumed; + mDragState.endDragLw(); + } + } + + void cancelDragAndDrop(WindowManagerService service, IBinder dragToken) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "cancelDragAndDrop"); + } + + synchronized (service.mWindowMap) { + if (mDragState == null) { + Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()"); + throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()"); + } + + if (mDragState.mToken != dragToken) { + Slog.w(TAG_WM, + "cancelDragAndDrop() does not match prepareDrag()"); + throw new IllegalStateException( + "cancelDragAndDrop() does not match prepareDrag()"); + } + + mDragState.mDragResult = false; + mDragState.cancelDragLw(); + } + } + + void dragRecipientEntered(IWindow window) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); + } + } + + void dragRecipientExited(IWindow window) { + if (DEBUG_DRAG) { + Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder()); + } + } + + void handleMessage(WindowManagerService service, Message msg) { + switch (msg.what) { + case H.DRAG_START_TIMEOUT: { + IBinder win = (IBinder) msg.obj; + if (DEBUG_DRAG) { + Slog.w(TAG_WM, "Timeout starting drag by win " + win); + } + synchronized (service.mWindowMap) { + // !!! TODO: ANR the app that has failed to start the drag in time + if (mDragState != null) { + mDragState.unregister(); + mDragState.reset(); + mDragState = null; + } + } + break; + } + + case H.DRAG_END_TIMEOUT: { + IBinder win = (IBinder) msg.obj; + if (DEBUG_DRAG) { + Slog.w(TAG_WM, "Timeout ending drag to win " + win); + } + synchronized (service.mWindowMap) { + // !!! TODO: ANR the drag-receiving app + if (mDragState != null) { + mDragState.mDragResult = false; + mDragState.endDragLw(); + } + } + break; + } + + case H.TEAR_DOWN_DRAG_AND_DROP_INPUT: { + if (DEBUG_DRAG) + Slog.d(TAG_WM, "Drag ending; tearing down input channel"); + DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj; + if (interceptor != null) { + synchronized (service.mWindowMap) { + interceptor.tearDown(); + } + } + break; + } + } + } +} diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java index 3fdafc7b..11f22411 100644 --- a/com/android/server/wm/DragState.java +++ b/com/android/server/wm/DragState.java @@ -22,6 +22,10 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.animation.Animator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.content.ClipData; import android.content.ClipDescription; import android.content.Context; @@ -29,7 +33,9 @@ import android.graphics.Matrix; import android.graphics.Point; import android.hardware.input.InputManager; import android.os.Build; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -44,16 +50,12 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.PointerIcon; import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.WindowManager; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; -import android.view.animation.ScaleAnimation; import android.view.animation.Transformation; -import android.view.animation.TranslateAnimation; import com.android.server.input.InputApplicationHandle; import com.android.server.input.InputWindowHandle; @@ -78,8 +80,20 @@ class DragState { View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION | View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION; + // Property names for animations + private static final String ANIMATED_PROPERTY_X = "x"; + private static final String ANIMATED_PROPERTY_Y = "y"; + private static final String ANIMATED_PROPERTY_ALPHA = "alpha"; + private static final String ANIMATED_PROPERTY_SCALE = "scale"; + + // Messages for Handler. + private static final int MSG_ANIMATION_END = 0; + final WindowManagerService mService; IBinder mToken; + /** + * Do not use the variable from the out of animation thread while mAnimator is not null. + */ SurfaceControl mSurfaceControl; int mFlags; IBinder mLocalWin; @@ -101,10 +115,10 @@ class DragState { boolean mDragInProgress; DisplayContent mDisplayContent; - private Animation mAnimation; - final Transformation mTransformation = new Transformation(); + @Nullable private ValueAnimator mAnimator; private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); private Point mDisplaySize = new Point(); + private final Handler mHandler; DragState(WindowManagerService service, IBinder token, SurfaceControl surface, int flags, IBinder localWin) { @@ -114,9 +128,14 @@ class DragState { mFlags = flags; mLocalWin = localWin; mNotifiedWindows = new ArrayList<WindowState>(); + mHandler = new DragStateHandler(service.mH.getLooper()); } void reset() { + if (mAnimator != null) { + Slog.wtf(TAG_WM, + "Unexpectedly destroying mSurfaceControl while animation is running"); + } if (mSurfaceControl != null) { mSurfaceControl.destroy(); } @@ -388,23 +407,33 @@ class DragState { } void endDragLw() { - if (mAnimation != null) { + if (mAnimator != null) { return; } if (!mDragResult) { - mAnimation = createReturnAnimationLocked(); - mService.scheduleAnimationLocked(); + mAnimator = createReturnAnimationLocked(); return; // Will call cleanUpDragLw when the animation is done. } cleanUpDragLw(); } void cancelDragLw() { - if (mAnimation != null) { + if (mAnimator != null) { + return; + } + if (!mDragInProgress) { + // This can happen if an app invokes Session#cancelDragAndDrop before + // Session#performDrag. Reset the drag state: + // 1. without sending the end broadcast because the start broadcast has not been sent, + // and + // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to + // WindowManagerService, which will cause DragState#reset() while playing the + // cancel animation. + reset(); + mService.mDragDropController.mDragState = null; return; } - mAnimation = createCancelAnimationLocked(); - mService.scheduleAnimationLocked(); + mAnimator = createCancelAnimationLocked(); } private void cleanUpDragLw() { @@ -418,11 +447,11 @@ class DragState { // free our resources and drop all the object references reset(); - mService.mDragState = null; + mService.mDragDropController.mDragState = null; } void notifyMoveLw(float x, float y) { - if (mAnimation != null) { + if (mAnimator != null) { return; } mCurrentX = x; @@ -491,7 +520,7 @@ class DragState { // dispatch the global drag-ended message, 'false' if we need to wait for a // result from the recipient. boolean notifyDropLw(float x, float y) { - if (mAnimation != null) { + if (mAnimator != null) { return false; } mCurrentX = x; @@ -560,56 +589,52 @@ class DragState { dragAndDropPermissions, result); } - boolean stepAnimationLocked(long currentTimeMs) { - if (mAnimation == null) { - return false; - } - - mTransformation.clear(); - if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) { - cleanUpDragLw(); - return false; - } - - mTransformation.getMatrix().postTranslate( - mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY); - final float tmpFloats[] = mService.mTmpFloats; - mTransformation.getMatrix().getValues(tmpFloats); - mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]); - mSurfaceControl.setAlpha(mTransformation.getAlpha()); - mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], - tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); - return true; - } + private ValueAnimator createReturnAnimationLocked() { + final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder( + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, + mOriginalX - mThumbOffsetX), + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, + mOriginalY - mThumbOffsetY), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1), + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2)); - private Animation createReturnAnimationLocked() { - final AnimationSet set = new AnimationSet(false); final float translateX = mOriginalX - mCurrentX; final float translateY = mOriginalY - mCurrentY; - set.addAnimation(new TranslateAnimation( 0, translateX, 0, translateY)); - set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2)); // Adjust the duration to the travel distance. final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY); final double displayDiagonal = Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y); final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS)); - set.setDuration(duration); - set.setInterpolator(mCubicEaseOutInterpolator); - set.initialize(0, 0, 0, 0); - set.start(); // Will start on the first call to getTransformation. - return set; + final AnimationListener listener = new AnimationListener(); + animator.setDuration(duration); + animator.setInterpolator(mCubicEaseOutInterpolator); + animator.addListener(listener); + animator.addUpdateListener(listener); + + mService.mAnimationHandler.post(() -> animator.start()); + return animator; } - private Animation createCancelAnimationLocked() { - final AnimationSet set = new AnimationSet(false); - set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY)); - set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0)); - set.setDuration(MIN_ANIMATION_DURATION_MS); - set.setInterpolator(mCubicEaseOutInterpolator); - set.initialize(0, 0, 0, 0); - set.start(); // Will start on the first call to getTransformation. - return set; + private ValueAnimator createCancelAnimationLocked() { + final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder( + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX), + PropertyValuesHolder.ofFloat( + ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0), + PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0)); + final AnimationListener listener = new AnimationListener(); + animator.setDuration(MIN_ANIMATION_DURATION_MS); + animator.setInterpolator(mCubicEaseOutInterpolator); + animator.addListener(listener); + animator.addUpdateListener(listener); + + mService.mAnimationHandler.post(() -> animator.start()); + return animator; } private boolean isFromSource(int source) { @@ -622,4 +647,68 @@ class DragState { InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING); } } + + private class DragStateHandler extends Handler { + DragStateHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ANIMATION_END: + synchronized (mService.mWindowMap) { + if (mService.mDragDropController.mDragState != DragState.this) { + Slog.wtf(TAG_WM, "mDragState is updated unexpectedly while " + + "playing animation"); + return; + } + if (mAnimator == null) { + Slog.wtf(TAG_WM, "Unexpected null mAnimator"); + return; + } + mAnimator = null; + cleanUpDragLw(); + } + break; + } + } + } + + private class AnimationListener + implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { + transaction.setPosition( + mSurfaceControl, + (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X), + (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y)); + transaction.setAlpha( + mSurfaceControl, + (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA)); + transaction.setMatrix( + mSurfaceControl, + (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0, + 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE)); + transaction.apply(); + } + } + + @Override + public void onAnimationStart(Animator animator) {} + + @Override + public void onAnimationCancel(Animator animator) {} + + @Override + public void onAnimationRepeat(Animator animator) {} + + @Override + public void onAnimationEnd(Animator animator) { + // Updating mDragState requires the WM lock so continues it on the out of + // AnimationThread. + mHandler.sendEmptyMessage(MSG_ANIMATION_END); + } + } } diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java index 19bd8e9d..8bec8d75 100644 --- a/com/android/server/wm/EmulatorDisplayOverlay.java +++ b/com/android/server/wm/EmulatorDisplayOverlay.java @@ -56,8 +56,11 @@ class EmulatorDisplayOverlay { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x, - mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + ctrl = new SurfaceControl.Builder(session) + .setName("EmulatorDisplayOverlay") + .setSize(mScreenSize.x, mScreenSize.y) + .setFormat(PixelFormat.TRANSLUCENT) + .build(); ctrl.setLayerStack(display.getLayerStack()); ctrl.setLayer(zOrder); ctrl.setPosition(0, 0); diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java index 238cb9f1..cf1f1713 100644 --- a/com/android/server/wm/InputMonitor.java +++ b/com/android/server/wm/InputMonitor.java @@ -367,12 +367,13 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { // currently has touch focus. // If there's a drag in flight, provide a pseudo-window to catch drag input - final boolean inDrag = (mService.mDragState != null); + final boolean inDrag = mService.mDragDropController.dragDropActiveLocked(); if (inDrag) { if (DEBUG_DRAG) { Log.d(TAG_WM, "Inserting drag window"); } - final InputWindowHandle dragWindowHandle = mService.mDragState.getInputWindowHandle(); + final InputWindowHandle dragWindowHandle = + mService.mDragDropController.mDragState.getInputWindowHandle(); if (dragWindowHandle != null) { addInputWindowHandle(dragWindowHandle); } else { @@ -689,7 +690,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { // If there's a drag in progress and 'child' is a potential drop target, // make sure it's been told about the drag if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) { - mService.mDragState.sendDragStartedIfNeededLw(w); + mService.mDragDropController.mDragState.sendDragStartedIfNeededLw(w); } addInputWindowHandle( diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java index fd574709..a7104410 100644 --- a/com/android/server/wm/RootWindowContainer.java +++ b/com/android/server/wm/RootWindowContainer.java @@ -1081,19 +1081,21 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { @CallSuper @Override - public void writeToProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { final long token = proto.start(fieldId); - super.writeToProto(proto, WINDOW_CONTAINER); + super.writeToProto(proto, WINDOW_CONTAINER, trim); if (mService.mDisplayReady) { final int count = mChildren.size(); for (int i = 0; i < count; ++i) { final DisplayContent displayContent = mChildren.get(i); - displayContent.writeToProto(proto, DISPLAYS); + displayContent.writeToProto(proto, DISPLAYS, trim); } } - forAllWindows((w) -> { - w.writeIdentifierToProto(proto, WINDOWS); - }, true); + if (!trim) { + forAllWindows((w) -> { + w.writeIdentifierToProto(proto, WINDOWS); + }, true); + } proto.end(token); } diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java index 8e99be83..c25b19cc 100644 --- a/com/android/server/wm/ScreenRotationAnimation.java +++ b/com/android/server/wm/ScreenRotationAnimation.java @@ -269,14 +269,11 @@ class ScreenRotationAnimation { try { try { - int flags = SurfaceControl.HIDDEN; - if (isSecure) { - flags |= SurfaceControl.SECURE; - } - - mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface", - mWidth, mHeight, - PixelFormat.OPAQUE, flags); + mSurfaceControl = new SurfaceControl.Builder(session) + .setName("ScreenshotSurface") + .setSize(mWidth, mHeight) + .setSecure(isSecure) + .build(); // capture a screenshot into the surface we just created Surface sur = new Surface(); diff --git a/com/android/server/wm/Session.java b/com/android/server/wm/Session.java index 4dd147e5..717c5774 100644 --- a/com/android/server/wm/Session.java +++ b/com/android/server/wm/Session.java @@ -68,9 +68,7 @@ import java.util.Set; * This class represents an active client session. There is generally one * Session object per process that is interacting with the window manager. */ -// Needs to be public and not final so we can mock during tests...sucks I know :( -public class Session extends IWindowSession.Stub - implements IBinder.DeathRecipient { +class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final WindowManagerService mService; final IWindowSessionCallback mCallback; final IInputMethodClient mClient; @@ -83,6 +81,7 @@ public class Session extends IWindowSession.Stub private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>(); // Set of visible alert window surfaces connected to this session. private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>(); + private final DragDropController mDragDropController; final boolean mCanAddInternalSystemWindow; final boolean mCanHideNonSystemOverlayWindows; final boolean mCanAcquireSleepToken; @@ -108,6 +107,7 @@ public class Session extends IWindowSession.Stub mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER) == PERMISSION_GRANTED; mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications; + mDragDropController = mService.mDragDropController; StringBuilder sb = new StringBuilder(); sb.append("Session{"); sb.append(Integer.toHexString(System.identityHashCode(this))); @@ -306,190 +306,68 @@ public class Session extends IWindowSession.Stub /* Drag/drop */ @Override - public IBinder prepareDrag(IWindow window, int flags, - int width, int height, Surface outSurface) { - return mService.prepareDragSurface(window, mSurfaceSession, flags, - width, height, outSurface); + public IBinder prepareDrag(IWindow window, int flags, int width, int height, + Surface outSurface) { + final int callerPid = Binder.getCallingPid(); + final int callerUid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + return mDragDropController.prepareDrag( + mService, mSurfaceSession, callerPid, callerUid, window, flags, width, height, + outSurface); + } finally { + Binder.restoreCallingIdentity(ident); + } } @Override public boolean performDrag(IWindow window, IBinder dragToken, int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data); - } - - synchronized (mService.mWindowMap) { - if (mService.mDragState == null) { - Slog.w(TAG_WM, "No drag prepared"); - throw new IllegalStateException("performDrag() without prepareDrag()"); - } - - if (dragToken != mService.mDragState.mToken) { - Slog.w(TAG_WM, "Performing mismatched drag"); - throw new IllegalStateException("performDrag() does not match prepareDrag()"); - } - - WindowState callingWin = mService.windowForClientLocked(null, window, false); - if (callingWin == null) { - Slog.w(TAG_WM, "Bad requesting window " + window); - return false; // !!! TODO: throw here? - } - - // !!! TODO: if input is not still focused on the initiating window, fail - // the drag initiation (e.g. an alarm window popped up just as the application - // called performDrag() - - mService.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder()); - - // !!! TODO: extract the current touch (x, y) in screen coordinates. That - // will let us eliminate the (touchX,touchY) parameters from the API. - - // !!! FIXME: put all this heavy stuff onto the mH looper, as well as - // the actual drag event dispatch stuff in the dragstate - - final DisplayContent displayContent = callingWin.getDisplayContent(); - if (displayContent == null) { - return false; - } - Display display = displayContent.getDisplay(); - mService.mDragState.register(display); - if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel, - mService.mDragState.getInputChannel())) { - Slog.e(TAG_WM, "Unable to transfer touch focus"); - mService.mDragState.unregister(); - mService.mDragState.reset(); - mService.mDragState = null; - return false; - } - - mService.mDragState.mDisplayContent = displayContent; - mService.mDragState.mData = data; - mService.mDragState.broadcastDragStartedLw(touchX, touchY); - mService.mDragState.overridePointerIconLw(touchSource); - - // remember the thumb offsets for later - mService.mDragState.mThumbOffsetX = thumbCenterX; - mService.mDragState.mThumbOffsetY = thumbCenterY; - - // Make the surface visible at the proper location - final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl; - if (SHOW_LIGHT_TRANSACTIONS) Slog.i( - TAG_WM, ">>> OPEN TRANSACTION performDrag"); - mService.openSurfaceTransaction(); - try { - surfaceControl.setPosition(touchX - thumbCenterX, - touchY - thumbCenterY); - surfaceControl.setLayer(mService.mDragState.getDragLayerLw()); - surfaceControl.setLayerStack(display.getLayerStack()); - surfaceControl.show(); - } finally { - mService.closeSurfaceTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i( - TAG_WM, "<<< CLOSE TRANSACTION performDrag"); - } - - mService.mDragState.notifyLocationLw(touchX, touchY); - } - - return true; // success! + return mDragDropController.performDrag(mService, window, dragToken, touchSource, + touchX, touchY, thumbCenterX, thumbCenterY, data); } @Override - public boolean startMovingTask(IWindow window, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) Slog.d( - TAG_WM, "startMovingTask: {" + startX + "," + startY + "}"); - - long ident = Binder.clearCallingIdentity(); + public void reportDropResult(IWindow window, boolean consumed) { + final long ident = Binder.clearCallingIdentity(); try { - return mService.startMovingTask(window, startX, startY); + mDragDropController.reportDropResult(mService, window, consumed); } finally { Binder.restoreCallingIdentity(ident); } } @Override - public void reportDropResult(IWindow window, boolean consumed) { - IBinder token = window.asBinder(); - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); - } - - synchronized (mService.mWindowMap) { - long ident = Binder.clearCallingIdentity(); - try { - if (mService.mDragState == null) { - // Most likely the drop recipient ANRed and we ended the drag - // out from under it. Log the issue and move on. - Slog.w(TAG_WM, "Drop result given but no drag in progress"); - return; - } - - if (mService.mDragState.mToken != token) { - // We're in a drag, but the wrong window has responded. - Slog.w(TAG_WM, "Invalid drop-result claim by " + window); - throw new IllegalStateException("reportDropResult() by non-recipient"); - } - - // The right window has responded, even if it's no longer around, - // so be sure to halt the timeout even if the later WindowState - // lookup fails. - mService.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder()); - WindowState callingWin = mService.windowForClientLocked(null, window, false); - if (callingWin == null) { - Slog.w(TAG_WM, "Bad result-reporting window " + window); - return; // !!! TODO: throw here? - } - - mService.mDragState.mDragResult = consumed; - mService.mDragState.endDragLw(); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - @Override public void cancelDragAndDrop(IBinder dragToken) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "cancelDragAndDrop"); - } - - synchronized (mService.mWindowMap) { - long ident = Binder.clearCallingIdentity(); - try { - if (mService.mDragState == null) { - Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()"); - throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()"); - } - - if (mService.mDragState.mToken != dragToken) { - Slog.w(TAG_WM, - "cancelDragAndDrop() does not match prepareDrag()"); - throw new IllegalStateException( - "cancelDragAndDrop() does not match prepareDrag()"); - } - - mService.mDragState.mDragResult = false; - mService.mDragState.cancelDragLw(); - } finally { - Binder.restoreCallingIdentity(ident); - } + final long ident = Binder.clearCallingIdentity(); + try { + mDragDropController.cancelDragAndDrop(mService, dragToken); + } finally { + Binder.restoreCallingIdentity(ident); } } @Override public void dragRecipientEntered(IWindow window) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); - } + mDragDropController.dragRecipientEntered(window); } @Override public void dragRecipientExited(IWindow window) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder()); + mDragDropController.dragRecipientExited(window); + } + + @Override + public boolean startMovingTask(IWindow window, float startX, float startY) { + if (DEBUG_TASK_POSITIONING) Slog.d( + TAG_WM, "startMovingTask: {" + startX + "," + startY + "}"); + + long ident = Binder.clearCallingIdentity(); + try { + return mService.startMovingTask(window, startX, startY); + } finally { + Binder.restoreCallingIdentity(ident); } } diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java index 1fda832d..aff1bc62 100644 --- a/com/android/server/wm/StackWindowController.java +++ b/com/android/server/wm/StackWindowController.java @@ -152,7 +152,8 @@ public class StackWindowController } } - public void positionChildAtBottom(TaskWindowContainerController child) { + public void positionChildAtBottom(TaskWindowContainerController child, + boolean includingParents) { if (child == null) { // TODO: Fix the call-points that cause this to happen. return; @@ -164,7 +165,7 @@ public class StackWindowController Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found"); return; } - mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */); + mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents); if (mService.mAppTransition.isTransitionSet()) { childTask.setSendingToBottom(true); diff --git a/com/android/server/wm/StrictModeFlash.java b/com/android/server/wm/StrictModeFlash.java index d1547eb0..eb8ee696 100644 --- a/com/android/server/wm/StrictModeFlash.java +++ b/com/android/server/wm/StrictModeFlash.java @@ -44,8 +44,11 @@ class StrictModeFlash { public StrictModeFlash(Display display, SurfaceSession session) { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl(session, "StrictModeFlash", - 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + ctrl = new SurfaceControl.Builder(session) + .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); diff --git a/com/android/server/wm/SurfaceControlWithBackground.java b/com/android/server/wm/SurfaceControlWithBackground.java index b0eaf148..a5080d57 100644 --- a/com/android/server/wm/SurfaceControlWithBackground.java +++ b/com/android/server/wm/SurfaceControlWithBackground.java @@ -66,10 +66,10 @@ class SurfaceControlWithBackground extends SurfaceControl { mWindowSurfaceController = other.mWindowSurfaceController; } - public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format, - int flags, int windowType, int ownerUid, + public SurfaceControlWithBackground(String name, SurfaceControl.Builder b, + int windowType, int w, int h, WindowSurfaceController windowSurfaceController) throws OutOfResourcesException { - super(s, name, w, h, format, flags, windowType, ownerUid); + super(b.build()); // We should only show background behind app windows that are letterboxed in a task. if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING) @@ -80,9 +80,11 @@ class SurfaceControlWithBackground extends SurfaceControl { mLastWidth = w; mLastHeight = h; mWindowSurfaceController.getContainerRect(mTmpContainerRect); - mBackgroundControl = new SurfaceControl(s, "Background for - " + name, - mTmpContainerRect.width(), mTmpContainerRect.height(), PixelFormat.OPAQUE, - flags | SurfaceControl.FX_SURFACE_DIM); + mBackgroundControl = b.setName("Background for - " + name) + .setSize(mTmpContainerRect.width(), mTmpContainerRect.height()) + .setFormat(OPAQUE) + .setColorLayer(true) + .build(); } @Override diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java index 891d637a..7620cb0d 100644 --- a/com/android/server/wm/Task.java +++ b/com/android/server/wm/Task.java @@ -725,13 +725,13 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU @CallSuper @Override - public void writeToProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { final long token = proto.start(fieldId); - super.writeToProto(proto, WINDOW_CONTAINER); + super.writeToProto(proto, WINDOW_CONTAINER, trim); proto.write(ID, mTaskId); for (int i = mChildren.size() - 1; i >= 0; i--) { final AppWindowToken appWindowToken = mChildren.get(i); - appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS); + appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS, trim); } proto.write(FILLS_PARENT, mFillsParent); mBounds.writeToProto(proto, BOUNDS); diff --git a/com/android/server/wm/TaskSnapshotSurface.java b/com/android/server/wm/TaskSnapshotSurface.java index 4698d725..3ce090ac 100644 --- a/com/android/server/wm/TaskSnapshotSurface.java +++ b/com/android/server/wm/TaskSnapshotSurface.java @@ -304,9 +304,11 @@ class TaskSnapshotSurface implements StartingSurface { final SurfaceSession session = new SurfaceSession(mSurface); // Keep a reference to it such that it doesn't get destroyed when finalized. - mChildSurfaceControl = new SurfaceControl(session, - mTitle + " - task-snapshot-surface", - buffer.getWidth(), buffer.getHeight(), buffer.getFormat(), HIDDEN); + mChildSurfaceControl = new SurfaceControl.Builder(session) + .setName(mTitle + " - task-snapshot-surface") + .setSize(buffer.getWidth(), buffer.getHeight()) + .setFormat(buffer.getFormat()) + .build(); Surface surface = new Surface(); surface.copyFrom(mChildSurfaceControl); diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java index d170b6f2..791accf8 100644 --- a/com/android/server/wm/TaskStack.java +++ b/com/android/server/wm/TaskStack.java @@ -1008,10 +1008,13 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye void resetAdjustedForIme(boolean adjustBoundsNow) { if (adjustBoundsNow) { mImeWin = null; - mAdjustedForIme = false; mImeGoingAway = false; mAdjustImeAmount = 0f; mAdjustDividerAmount = 0f; + if (!mAdjustedForIme) { + return; + } + mAdjustedForIme = false; updateAdjustedBounds(); mService.setResizeDimLayer(false, getWindowingMode(), 1.0f); } else { @@ -1229,12 +1232,12 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye @CallSuper @Override - public void writeToProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { final long token = proto.start(fieldId); - super.writeToProto(proto, WINDOW_CONTAINER); + super.writeToProto(proto, WINDOW_CONTAINER, trim); proto.write(ID, mStackId); for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) { - mChildren.get(taskNdx).writeToProto(proto, TASKS); + mChildren.get(taskNdx).writeToProto(proto, TASKS, trim); } proto.write(FILLS_PARENT, mFillsParent); mBounds.writeToProto(proto, BOUNDS); diff --git a/com/android/server/wm/Watermark.java b/com/android/server/wm/Watermark.java index 171e575c..d97aaac4 100644 --- a/com/android/server/wm/Watermark.java +++ b/com/android/server/wm/Watermark.java @@ -114,8 +114,11 @@ class Watermark { SurfaceControl ctrl = null; try { - ctrl = new SurfaceControl(session, "WatermarkSurface", - 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + ctrl = new SurfaceControl.Builder(session) + .setName("WatermarkSurface") + .setSize(1, 1) + .setFormat(PixelFormat.TRANSLUCENT) + .build(); ctrl.setLayerStack(mDisplay.getLayerStack()); ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100); ctrl.setPosition(0, 0); diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java index c01ee31e..e409a68f 100644 --- a/com/android/server/wm/WindowAnimator.java +++ b/com/android/server/wm/WindowAnimator.java @@ -223,10 +223,6 @@ public class WindowAnimator { } } - if (mService.mDragState != null) { - mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime); - } - if (!mAnimating) { cancelAnimation(); } diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java index 563eb9cd..8f4b897c 100644 --- a/com/android/server/wm/WindowContainer.java +++ b/com/android/server/wm/WindowContainer.java @@ -677,17 +677,18 @@ a * Returns whether this child is on top of the window hierarchy. * Write to a protocol buffer output stream. Protocol buffer message definition is at * {@link com.android.server.wm.proto.WindowContainerProto}. * - * @param protoOutputStream Stream to write the WindowContainer object to. - * @param fieldId Field Id of the WindowContainer as defined in the parent message. + * @param proto Stream to write the WindowContainer object to. + * @param fieldId Field Id of the WindowContainer as defined in the parent message. + * @param trim If true, reduce the amount of data written. * @hide */ @CallSuper @Override - public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { - final long token = protoOutputStream.start(fieldId); - super.writeToProto(protoOutputStream, CONFIGURATION_CONTAINER); - protoOutputStream.write(ORIENTATION, mOrientation); - protoOutputStream.end(token); + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { + final long token = proto.start(fieldId); + super.writeToProto(proto, CONFIGURATION_CONTAINER, trim); + proto.write(ORIENTATION, mOrientation); + proto.end(token); } private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) { diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java index f0da4742..f31ea67c 100644 --- a/com/android/server/wm/WindowManagerService.java +++ b/com/android/server/wm/WindowManagerService.java @@ -357,8 +357,6 @@ public class WindowManagerService extends IWindowManager.Stub // trying to apply a new one. private static final boolean ALWAYS_KEEP_CURRENT = true; - private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; - // Enums for animation scale update types. @Retention(RetentionPolicy.SOURCE) @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE}) @@ -394,13 +392,14 @@ public class WindowManagerService extends IWindowManager.Stub private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { @Override - public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) { - doDump(fd, pw, new String[] {"-a"}); + public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, + boolean asProto) { + doDump(fd, pw, new String[] {"-a"}, asProto); } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - doDump(fd, pw, args); + public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { + doDump(fd, pw, args, asProto); } }; @@ -752,7 +751,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean mAllowTheaterModeWakeFromLayout; TaskPositioner mTaskPositioner; - DragState mDragState = null; + final DragDropController mDragDropController = new DragDropController(); // For frozen screen animations. private int mExitAnimId, mEnterAnimId; @@ -789,7 +788,7 @@ public class WindowManagerService extends IWindowManager.Stub public void onInputEvent(InputEvent event, int displayId) { boolean handled = false; try { - if (mDragState == null) { + if (mDragDropController.mDragState == null) { // The drag has ended but the clean-up message has not been processed by // window manager. Drop events that occur after this until window manager // has a chance to clean-up the input handle. @@ -828,12 +827,12 @@ public class WindowManagerService extends IWindowManager.Stub + newX + "," + newY); mMuteInput = true; synchronized (mWindowMap) { - endDrag = mDragState.notifyDropLw(newX, newY); + endDrag = mDragDropController.mDragState.notifyDropLw(newX, newY); } } else { synchronized (mWindowMap) { // move the surface and tell the involved window(s) where we are - mDragState.notifyMoveLw(newX, newY); + mDragDropController.mDragState.notifyMoveLw(newX, newY); } } } break; @@ -843,7 +842,7 @@ public class WindowManagerService extends IWindowManager.Stub + newX + "," + newY); mMuteInput = true; synchronized (mWindowMap) { - endDrag = mDragState.notifyDropLw(newX, newY); + endDrag = mDragDropController.mDragState.notifyDropLw(newX, newY); } } break; @@ -860,7 +859,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { // endDragLw will post back to looper to dispose the receiver // since we still need the receiver for the last finishInputEvent. - mDragState.endDragLw(); + mDragDropController.mDragState.endDragLw(); } mStylusButtonDownAtStart = false; mIsStartEvent = true; @@ -4637,73 +4636,6 @@ public class WindowManagerService extends IWindowManager.Stub } // ------------------------------------------------------------- - // Drag and drop - // ------------------------------------------------------------- - - IBinder prepareDragSurface(IWindow window, SurfaceSession session, - int flags, int width, int height, Surface outSurface) { - if (DEBUG_DRAG) { - Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height - + " flags=" + Integer.toHexString(flags) + " win=" + window - + " asbinder=" + window.asBinder()); - } - - final int callerPid = Binder.getCallingPid(); - final int callerUid = Binder.getCallingUid(); - final long origId = Binder.clearCallingIdentity(); - IBinder token = null; - - try { - synchronized (mWindowMap) { - try { - if (mDragState == null) { - // TODO(multi-display): support other displays - final DisplayContent displayContent = getDefaultDisplayContentLocked(); - final Display display = displayContent.getDisplay(); - - SurfaceControl surface = new SurfaceControl(session, "drag surface", - width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); - surface.setLayerStack(display.getLayerStack()); - float alpha = 1; - if ((flags & View.DRAG_FLAG_OPAQUE) == 0) { - alpha = DRAG_SHADOW_ALPHA_TRANSPARENT; - } - surface.setAlpha(alpha); - - if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " - + surface + ": CREATE"); - outSurface.copyFrom(surface); - final IBinder winBinder = window.asBinder(); - token = new Binder(); - mDragState = new DragState(this, token, surface, flags, winBinder); - mDragState.mPid = callerPid; - mDragState.mUid = callerUid; - mDragState.mOriginalAlpha = alpha; - token = mDragState.mToken = new Binder(); - - // 5 second timeout for this window to actually begin the drag - mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder); - Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder); - mH.sendMessageDelayed(msg, 5000); - } else { - Slog.w(TAG_WM, "Drag already in progress"); - } - } catch (OutOfResourcesException e) { - Slog.e(TAG_WM, "Can't allocate drag surface w=" + width + " h=" + height, e); - if (mDragState != null) { - mDragState.reset(); - mDragState = null; - } - } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - - return token; - } - - // ------------------------------------------------------------- // Input Events and Focus Management // ------------------------------------------------------------- @@ -4866,6 +4798,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int REPORT_WINDOWS_CHANGE = 19; public static final int DRAG_START_TIMEOUT = 20; public static final int DRAG_END_TIMEOUT = 21; + public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22; public static final int BOOT_TIMEOUT = 23; public static final int WAITING_FOR_DRAWN_TIMEOUT = 24; @@ -5120,47 +5053,12 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case DRAG_START_TIMEOUT: { - IBinder win = (IBinder)msg.obj; - if (DEBUG_DRAG) { - Slog.w(TAG_WM, "Timeout starting drag by win " + win); - } - synchronized (mWindowMap) { - // !!! TODO: ANR the app that has failed to start the drag in time - if (mDragState != null) { - mDragState.unregister(); - mDragState.reset(); - mDragState = null; - } - } - break; - } - - case DRAG_END_TIMEOUT: { - IBinder win = (IBinder)msg.obj; - if (DEBUG_DRAG) { - Slog.w(TAG_WM, "Timeout ending drag to win " + win); - } - synchronized (mWindowMap) { - // !!! TODO: ANR the drag-receiving app - if (mDragState != null) { - mDragState.mDragResult = false; - mDragState.endDragLw(); - } - } - break; - } - + case DRAG_START_TIMEOUT: + case DRAG_END_TIMEOUT: case TEAR_DOWN_DRAG_AND_DROP_INPUT: { - if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ending; tearing down input channel"); - DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj; - if (interceptor != null) { - synchronized (mWindowMap) { - interceptor.tearDown(); - } - } + mDragDropController.handleMessage(WindowManagerService.this, msg); + break; } - break; case REPORT_HARD_KEYBOARD_STATUS_CHANGE: { notifyHardKeyboardStatusChange(); @@ -6532,9 +6430,16 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void writeToProtoLocked(ProtoOutputStream proto) { + /** + * Write to a protocol buffer output stream. Protocol buffer message definition is at + * {@link com.android.server.wm.proto.WindowManagerServiceProto}. + * + * @param proto Stream to write the WindowContainer object to. + * @param trim If true, reduce the amount of data written. + */ + private void writeToProtoLocked(ProtoOutputStream proto, boolean trim) { mPolicy.writeToProto(proto, POLICY); - mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER); + mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim); if (mCurrentFocus != null) { mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW); } @@ -6825,10 +6730,9 @@ public class WindowManagerService extends IWindowManager.Stub PriorityDump.dump(mPriorityDumper, fd, pw, args); } - private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) { + private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; boolean dumpAll = false; - boolean useProto = false; int opti = 0; while (opti < args.length) { @@ -6839,8 +6743,6 @@ public class WindowManagerService extends IWindowManager.Stub opti++; if ("-a".equals(opt)) { dumpAll = true; - } else if ("--proto".equals(opt)) { - useProto = true; } else if ("-h".equals(opt)) { pw.println("Window manager dump options:"); pw.println(" [-a] [-h] [cmd] ..."); @@ -6870,7 +6772,7 @@ public class WindowManagerService extends IWindowManager.Stub if (useProto) { final ProtoOutputStream proto = new ProtoOutputStream(fd); synchronized (mWindowMap) { - writeToProtoLocked(proto); + writeToProtoLocked(proto, false /* trim */); } proto.flush(); return; @@ -7268,7 +7170,7 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized (mWindowMap) { - if (mDragState != null) { + if (mDragDropController.mDragState != null) { // Drag cursor overrides the app cursor. return; } @@ -7702,7 +7604,8 @@ public class WindowManagerService extends IWindowManager.Stub } boolean hasWideColorGamutSupport() { - return mHasWideColorGamutSupport; + return mHasWideColorGamutSupport && + !SystemProperties.getBoolean("persist.sys.sf.native_mode", false); } void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) { diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java index e1715284..4370a763 100644 --- a/com/android/server/wm/WindowState.java +++ b/com/android/server/wm/WindowState.java @@ -3120,9 +3120,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @CallSuper @Override - public void writeToProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { final long token = proto.start(fieldId); - super.writeToProto(proto, WINDOW_CONTAINER); + super.writeToProto(proto, WINDOW_CONTAINER, trim); writeIdentifierToProto(proto, IDENTIFIER); proto.write(DISPLAY_ID, getDisplayId()); proto.write(STACK_ID, getStackId()); @@ -3137,7 +3137,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWinAnimator.writeToProto(proto, ANIMATOR); proto.write(ANIMATING_EXIT, mAnimatingExit); for (int i = 0; i < mChildren.size(); i++) { - mChildren.get(i).writeToProto(proto, CHILD_WINDOWS); + mChildren.get(i).writeToProto(proto, CHILD_WINDOWS, trim); } proto.end(token); } diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java index d56df55d..edd650a4 100644 --- a/com/android/server/wm/WindowSurfaceController.java +++ b/com/android/server/wm/WindowSurfaceController.java @@ -101,8 +101,14 @@ class WindowSurfaceController { mWindowSession = win.mSession; Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl"); + final SurfaceControl.Builder b = new SurfaceControl.Builder(s) + .setName(name) + .setSize(w, h) + .setFormat(format) + .setFlags(flags) + .setMetadata(windowType, ownerUid); mSurfaceControl = new SurfaceControlWithBackground( - s, name, w, h, format, flags, windowType, ownerUid, this); + name, b, windowType, w, h, this); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (mService.mRoot.mSurfaceTraceEnabled) { @@ -246,7 +252,7 @@ class WindowSurfaceController { if (mAnimator.mWin.usesRelativeZOrdering()) { mSurfaceControl.setRelativeLayer( mAnimator.mWin.getParentWindow() - .mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(), + .mWinAnimator.mSurfaceController.mSurfaceControl, -1); } else { mSurfaceLayer = layer; diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java index fa33fe8f..d57fdd26 100644 --- a/com/android/server/wm/WindowSurfacePlacer.java +++ b/com/android/server/wm/WindowSurfacePlacer.java @@ -1,3 +1,18 @@ +/* + * 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; @@ -689,11 +704,14 @@ class WindowSurfacePlacer { // Create a new surface for the thumbnail WindowState window = appToken.findMainWindow(); - SurfaceControl surfaceControl = new SurfaceControl(mService.mFxSession, - "thumbnail anim", dirty.width(), dirty.height(), - PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN, - appToken.windowType, - window != null ? window.mOwnerUid : Binder.getCallingUid()); + final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession) + .setName("thumbnail anim") + .setSize(dirty.width(), dirty.height()) + .setFormat(PixelFormat.TRANSLUCENT) + .setMetadata(appToken.windowType, + window != null ? window.mOwnerUid : Binder.getCallingUid()) + .build(); + surfaceControl.setLayerStack(display.getLayerStack()); if (SHOW_TRANSACTIONS) { Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE"); diff --git a/com/android/server/wm/WindowToken.java b/com/android/server/wm/WindowToken.java index 943448ee..62a2abbb 100644 --- a/com/android/server/wm/WindowToken.java +++ b/com/android/server/wm/WindowToken.java @@ -267,13 +267,13 @@ class WindowToken extends WindowContainer<WindowState> { @CallSuper @Override - public void writeToProto(ProtoOutputStream proto, long fieldId) { + public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { final long token = proto.start(fieldId); - super.writeToProto(proto, WINDOW_CONTAINER); + super.writeToProto(proto, WINDOW_CONTAINER, trim); proto.write(HASH_CODE, System.identityHashCode(this)); for (int i = 0; i < mChildren.size(); i++) { final WindowState w = mChildren.get(i); - w.writeToProto(proto, WINDOWS); + w.writeToProto(proto, WINDOWS, trim); } proto.end(token); } diff --git a/com/android/settingslib/bluetooth/A2dpProfile.java b/com/android/settingslib/bluetooth/A2dpProfile.java index 0946181a..764c5922 100644 --- a/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/com/android/settingslib/bluetooth/A2dpProfile.java @@ -30,6 +30,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; +import com.android.settingslib.wrapper.BluetoothA2dpWrapper; import java.util.ArrayList; import java.util.Arrays; @@ -42,7 +43,6 @@ public class A2dpProfile implements LocalBluetoothProfile { private Context mContext; private BluetoothA2dp mService; - BluetoothA2dpWrapper.Factory mWrapperFactory; private BluetoothA2dpWrapper mServiceWrapper; private boolean mIsProfileReady; @@ -67,7 +67,7 @@ public class A2dpProfile implements LocalBluetoothProfile { public void onServiceConnected(int profile, BluetoothProfile proxy) { if (V) Log.d(TAG,"Bluetooth service connected"); mService = (BluetoothA2dp) proxy; - mServiceWrapper = mWrapperFactory.getInstance(mService); + mServiceWrapper = new BluetoothA2dpWrapper(mService); // We just bound to the service, so refresh the UI for any connected A2DP devices. List<BluetoothDevice> deviceList = mService.getConnectedDevices(); while (!deviceList.isEmpty()) { @@ -101,14 +101,13 @@ public class A2dpProfile implements LocalBluetoothProfile { mLocalAdapter = adapter; mDeviceManager = deviceManager; mProfileManager = profileManager; - mWrapperFactory = new BluetoothA2dpWrapperImpl.Factory(); mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(), BluetoothProfile.A2DP); } @VisibleForTesting - void setWrapperFactory(BluetoothA2dpWrapper.Factory factory) { - mWrapperFactory = factory; + void setBluetoothA2dpWrapper(BluetoothA2dpWrapper wrapper) { + mServiceWrapper = wrapper; } public boolean isConnectable() { diff --git a/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java b/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java deleted file mode 100644 index aa3e8356..00000000 --- a/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java +++ /dev/null @@ -1,58 +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.settingslib.bluetooth; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothCodecStatus; -import android.bluetooth.BluetoothDevice; - -/** - * This interface replicates some methods of android.bluetooth.BluetoothA2dp that are new and not - * yet available in our current version of Robolectric. It provides a thin wrapper to call the real - * methods in production and a mock in tests. - */ -public interface BluetoothA2dpWrapper { - - static interface Factory { - BluetoothA2dpWrapper getInstance(BluetoothA2dp service); - } - - /** - * @return the real {@code BluetoothA2dp} object - */ - BluetoothA2dp getService(); - - /** - * Wraps {@code BluetoothA2dp.getCodecStatus} - */ - public BluetoothCodecStatus getCodecStatus(); - - /** - * Wraps {@code BluetoothA2dp.supportsOptionalCodecs} - */ - int supportsOptionalCodecs(BluetoothDevice device); - - /** - * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled} - */ - int getOptionalCodecsEnabled(BluetoothDevice device); - - /** - * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled} - */ - void setOptionalCodecsEnabled(BluetoothDevice device, int value); -} diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java index 35ba6ae9..038dcf84 100644 --- a/com/android/settingslib/drawer/TileUtils.java +++ b/com/android/settingslib/drawer/TileUtils.java @@ -65,7 +65,7 @@ public class TileUtils { * * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY} */ - private static final String EXTRA_SETTINGS_ACTION = + public static final String EXTRA_SETTINGS_ACTION = "com.android.settings.action.EXTRA_SETTINGS"; /** diff --git a/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/com/android/settingslib/graph/BatteryMeterDrawableBase.java index 817989ab..f4c9bb31 100644 --- a/com/android/settingslib/graph/BatteryMeterDrawableBase.java +++ b/com/android/settingslib/graph/BatteryMeterDrawableBase.java @@ -415,8 +415,8 @@ public class BatteryMeterDrawableBase extends Drawable { : (mLevel == 100 ? 0.38f : 0.5f))); mTextHeight = -mTextPaint.getFontMetrics().ascent; pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level / 10) : level); - pctX = mWidth * 0.5f; - pctY = (mHeight + mTextHeight) * 0.47f; + pctX = mWidth * 0.5f + left; + pctY = (mHeight + mTextHeight) * 0.47f + top; pctOpaque = levelTop > pctY; if (!pctOpaque) { mTextPath.reset(); @@ -439,8 +439,8 @@ public class BatteryMeterDrawableBase extends Drawable { if (!mCharging && !mPowerSaveEnabled) { if (level <= mCriticalLevel) { // draw the warning text - final float x = mWidth * 0.5f; - final float y = (mHeight + mWarningTextHeight) * 0.48f; + final float x = mWidth * 0.5f + left; + final float y = (mHeight + mWarningTextHeight) * 0.48f + top; c.drawText(mWarningString, x, y, mWarningTextPaint); } else if (pctOpaque) { // draw the percentage text diff --git a/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java b/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java index 14fa7966..4c52a9f8 100644 --- a/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java +++ b/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java @@ -14,48 +14,56 @@ * limitations under the License. */ -package com.android.settingslib.bluetooth; +package com.android.settingslib.wrapper; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; -public class BluetoothA2dpWrapperImpl implements BluetoothA2dpWrapper { - - public static class Factory implements BluetoothA2dpWrapper.Factory { - @Override - public BluetoothA2dpWrapper getInstance(BluetoothA2dp service) { - return new BluetoothA2dpWrapperImpl(service); - } - } +/** + * This class replicates some methods of android.bluetooth.BluetoothA2dp that are new and not + * yet available in our current version of Robolectric. It provides a thin wrapper to call the real + * methods in production and a mock in tests. + */ +public class BluetoothA2dpWrapper { private BluetoothA2dp mService; - public BluetoothA2dpWrapperImpl(BluetoothA2dp service) { + public BluetoothA2dpWrapper(BluetoothA2dp service) { mService = service; } - @Override + /** + * @return the real {@code BluetoothA2dp} object + */ public BluetoothA2dp getService() { return mService; } - @Override + /** + * Wraps {@code BluetoothA2dp.getCodecStatus} + */ public BluetoothCodecStatus getCodecStatus() { return mService.getCodecStatus(); } - @Override + /** + * Wraps {@code BluetoothA2dp.supportsOptionalCodecs} + */ public int supportsOptionalCodecs(BluetoothDevice device) { return mService.supportsOptionalCodecs(device); } - @Override + /** + * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled} + */ public int getOptionalCodecsEnabled(BluetoothDevice device) { return mService.getOptionalCodecsEnabled(device); } - @Override + /** + * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled} + */ public void setOptionalCodecsEnabled(BluetoothDevice device, int value) { mService.setOptionalCodecsEnabled(device, value); } diff --git a/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/com/android/setupwizardlib/test/util/DrawingTestActivity.java index 3d11e128..154339a7 100644 --- a/com/android/setupwizardlib/test/util/DrawingTestActivity.java +++ b/com/android/setupwizardlib/test/util/DrawingTestActivity.java @@ -16,14 +16,15 @@ package com.android.setupwizardlib.test.util; -import android.app.Activity; +import android.support.v7.app.AppCompatActivity; /** * Activity to test view and drawable drawing behaviors. This is used to make sure that the drawing - * behavior tested is the same as it would when inflated as part of an activity, including any - * injected layout inflater factories and custom themes etc. + * behavior tested is the same as it would when inflated as part of an {@link AppCompatActivity}, + * including custom layout inflaters and theme values that the support library injects to the + * activity. * * @see DrawingTestHelper */ -public class DrawingTestActivity extends Activity { +public class DrawingTestActivity extends AppCompatActivity { } diff --git a/com/android/setupwizardlib/view/NavigationBarButton.java b/com/android/setupwizardlib/view/NavigationBarButton.java index 45d3737c..5172c476 100644 --- a/com/android/setupwizardlib/view/NavigationBarButton.java +++ b/com/android/setupwizardlib/view/NavigationBarButton.java @@ -17,16 +17,143 @@ package com.android.setupwizardlib.view; import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Build; +import android.support.annotation.NonNull; import android.util.AttributeSet; import android.widget.Button; +/** + * Button for navigation bar, which includes tinting of its compound drawables to be used for dark + * and light themes. + */ public class NavigationBarButton extends Button { public NavigationBarButton(Context context) { super(context); + init(); } public NavigationBarButton(Context context, AttributeSet attrs) { super(context, attrs); + init(); + } + + private void init() { + // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter, + // so manually getting it and wrapping it in the compat drawable. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Drawable[] drawables = getCompoundDrawablesRelative(); + for (int i = 0; i < drawables.length; i++) { + if (drawables[i] != null) { + drawables[i] = TintedDrawable.wrap(drawables[i]); + } + } + setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1], + drawables[2], drawables[3]); + } + } + + @Override + public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) { + if (left != null) left = TintedDrawable.wrap(left); + if (top != null) top = TintedDrawable.wrap(top); + if (right != null) right = TintedDrawable.wrap(right); + if (bottom != null) bottom = TintedDrawable.wrap(bottom); + super.setCompoundDrawables(left, top, right, bottom); + tintDrawables(); + } + + @Override + public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end, + Drawable bottom) { + if (start != null) start = TintedDrawable.wrap(start); + if (top != null) top = TintedDrawable.wrap(top); + if (end != null) end = TintedDrawable.wrap(end); + if (bottom != null) bottom = TintedDrawable.wrap(bottom); + super.setCompoundDrawablesRelative(start, top, end, bottom); + tintDrawables(); + } + + @Override + public void setTextColor(ColorStateList colors) { + super.setTextColor(colors); + tintDrawables(); + } + + private void tintDrawables() { + final ColorStateList textColors = getTextColors(); + if (textColors != null) { + for (Drawable drawable : getAllCompoundDrawables()) { + if (drawable instanceof TintedDrawable) { + ((TintedDrawable) drawable).setTintListCompat(textColors); + } + } + invalidate(); + } + } + + private Drawable[] getAllCompoundDrawables() { + Drawable[] drawables = new Drawable[6]; + Drawable[] compoundDrawables = getCompoundDrawables(); + drawables[0] = compoundDrawables[0]; // left + drawables[1] = compoundDrawables[1]; // top + drawables[2] = compoundDrawables[2]; // right + drawables[3] = compoundDrawables[3]; // bottom + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative(); + drawables[4] = compoundDrawablesRelative[0]; // start + drawables[5] = compoundDrawablesRelative[2]; // end + } + return drawables; + } + + // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0 + // or above + private static class TintedDrawable extends LayerDrawable { + + public static TintedDrawable wrap(Drawable drawable) { + if (drawable instanceof TintedDrawable) { + return (TintedDrawable) drawable; + } + return new TintedDrawable(drawable.mutate()); + } + + private ColorStateList mTintList = null; + + TintedDrawable(Drawable wrapped) { + super(new Drawable[] { wrapped }); + } + + @Override + public boolean isStateful() { + return true; + } + + @Override + public boolean setState(@NonNull int[] stateSet) { + boolean needsInvalidate = super.setState(stateSet); + boolean needsInvalidateForState = updateState(); + return needsInvalidate || needsInvalidateForState; + } + + public void setTintListCompat(ColorStateList colors) { + mTintList = colors; + if (updateState()) { + invalidateSelf(); + } + } + + private boolean updateState() { + if (mTintList != null) { + final int color = mTintList.getColorForState(getState(), 0); + setColorFilter(color, PorterDuff.Mode.SRC_IN); + return true; // Needs invalidate + } + return false; + } } } diff --git a/com/android/setupwizardlib/view/RichTextView.java b/com/android/setupwizardlib/view/RichTextView.java index 5a78561f..e6bc9da0 100644 --- a/com/android/setupwizardlib/view/RichTextView.java +++ b/com/android/setupwizardlib/view/RichTextView.java @@ -17,6 +17,11 @@ package com.android.setupwizardlib.view; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.AppCompatTextView; import android.text.Annotation; import android.text.SpannableString; import android.text.Spanned; @@ -25,22 +30,18 @@ import android.text.style.ClickableSpan; import android.text.style.TextAppearanceSpan; import android.util.AttributeSet; import android.util.Log; -import android.widget.TextView; +import android.view.MotionEvent; import com.android.setupwizardlib.span.LinkSpan; import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener; import com.android.setupwizardlib.span.SpanHelper; +import com.android.setupwizardlib.util.LinkAccessibilityHelper; /** * An extension of TextView that automatically replaces the annotation tags as specified in * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)} - * - * <p>Note: The accessibility interaction for ClickableSpans (and therefore LinkSpans) are built - * into platform in O, although the interaction paradigm is different. (See b/17726921). In this - * platform version, the links are exposed in the Local Context Menu of TalkBack instead of - * accessible directly through swiping. */ -public class RichTextView extends TextView implements OnLinkClickListener { +public class RichTextView extends AppCompatTextView implements OnLinkClickListener { /* static section */ @@ -88,14 +89,22 @@ public class RichTextView extends TextView implements OnLinkClickListener { /* non-static section */ + private LinkAccessibilityHelper mAccessibilityHelper; private OnLinkClickListener mOnLinkClickListener; public RichTextView(Context context) { super(context); + init(); } public RichTextView(Context context, AttributeSet attrs) { super(context, attrs); + init(); + } + + private void init() { + mAccessibilityHelper = new LinkAccessibilityHelper(this); + ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper); } @Override @@ -132,6 +141,32 @@ public class RichTextView extends TextView implements OnLinkClickListener { return false; } + @Override + protected boolean dispatchHoverEvent(MotionEvent event) { + if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) { + return true; + } + return super.dispatchHoverEvent(event); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { + // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a + // workaround, set the state on those drawables directly. + final int[] state = getDrawableState(); + for (Drawable drawable : getCompoundDrawablesRelative()) { + if (drawable != null) { + if (drawable.setState(state)) { + invalidateDrawable(drawable); + } + } + } + } + } + public void setOnLinkClickListener(OnLinkClickListener listener) { mOnLinkClickListener = listener; } diff --git a/com/android/systemui/ForegroundServiceControllerImpl.java b/com/android/systemui/ForegroundServiceControllerImpl.java index c930d567..3714c4ea 100644 --- a/com/android/systemui/ForegroundServiceControllerImpl.java +++ b/com/android/systemui/ForegroundServiceControllerImpl.java @@ -34,6 +34,10 @@ import java.util.Arrays; */ public class ForegroundServiceControllerImpl implements ForegroundServiceController { + + // shelf life of foreground services before they go bad + public static final long FG_SERVICE_GRACE_MILLIS = 5000; + private static final String TAG = "FgServiceController"; private static final boolean DBG = false; @@ -72,7 +76,7 @@ public class ForegroundServiceControllerImpl if (isDungeonNotification(sbn)) { // if you remove the dungeon entirely, we take that to mean there are // no running services - userServices.setRunningServices(null); + userServices.setRunningServices(null, 0); return true; } else { // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE @@ -94,7 +98,7 @@ public class ForegroundServiceControllerImpl final Bundle extras = sbn.getNotification().extras; if (extras != null) { final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS); - userServices.setRunningServices(svcs); // null ok + userServices.setRunningServices(svcs, sbn.getNotification().when); } } else { userServices.removeNotification(sbn.getPackageName(), sbn.getKey()); @@ -118,9 +122,11 @@ public class ForegroundServiceControllerImpl */ private static class UserServices { private String[] mRunning = null; + private long mServiceStartTime = 0; private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1); - public void setRunningServices(String[] pkgs) { + public void setRunningServices(String[] pkgs, long serviceStartTime) { mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null; + mServiceStartTime = serviceStartTime; } public void addNotification(String pkg, String key) { if (mNotifications.get(pkg) == null) { @@ -142,7 +148,9 @@ public class ForegroundServiceControllerImpl return found; } public boolean isDungeonNeeded() { - if (mRunning != null) { + if (mRunning != null + && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) { + for (String pkg : mRunning) { final ArraySet<String> set = mNotifications.get(pkg); if (set == null || set.size() == 0) { diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java index 312b9908..c92562b4 100644 --- a/com/android/systemui/pip/tv/PipManager.java +++ b/com/android/systemui/pip/tv/PipManager.java @@ -599,8 +599,8 @@ public class PipManager implements BasePipManager { private boolean isSettingsShown() { List<RunningTaskInfo> runningTasks; try { - runningTasks = mActivityManager.getTasks(1, 0); - if (runningTasks == null || runningTasks.size() == 0) { + runningTasks = mActivityManager.getTasks(1); + if (runningTasks.isEmpty()) { return false; } } catch (RemoteException e) { diff --git a/com/android/systemui/qs/QSDetailItems.java b/com/android/systemui/qs/QSDetailItems.java index 8869e8dd..ddd99102 100644 --- a/com/android/systemui/qs/QSDetailItems.java +++ b/com/android/systemui/qs/QSDetailItems.java @@ -185,10 +185,10 @@ public class QSDetailItems extends FrameLayout { } view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE); final ImageView iv = (ImageView) view.findViewById(android.R.id.icon); - if (item.iconDrawable != null) { - iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext())); + if (item.icon != null) { + iv.setImageDrawable(item.icon.getDrawable(iv.getContext())); } else { - iv.setImageResource(item.icon); + iv.setImageResource(item.iconResId); } iv.getOverlay().clear(); if (item.overlay != null) { @@ -258,8 +258,8 @@ public class QSDetailItems extends FrameLayout { } public static class Item { - public int icon; - public QSTile.Icon iconDrawable; + public int iconResId; + public QSTile.Icon icon; public Drawable overlay; public CharSequence line1; public CharSequence line2; diff --git a/com/android/systemui/qs/tiles/BluetoothTile.java b/com/android/systemui/qs/tiles/BluetoothTile.java index bc3ccb41..1aecdceb 100644 --- a/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/com/android/systemui/qs/tiles/BluetoothTile.java @@ -134,7 +134,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { if (lastDevice != null) { int batteryLevel = lastDevice.getBatteryLevel(); if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { - state.icon = new BluetoothBatteryDrawable(batteryLevel, + state.icon = new BluetoothBatteryTileIcon(batteryLevel, mContext.getResources().getFraction( R.fraction.bt_battery_scale_fraction, 1, 1)); } @@ -212,15 +212,11 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { return new BluetoothDetailAdapter(); } - private class BluetoothBatteryDrawable extends Icon { + private class BluetoothBatteryTileIcon extends Icon { private int mLevel; private float mIconScale; - BluetoothBatteryDrawable(int level) { - this(level, 1 /* iconScale */); - } - - BluetoothBatteryDrawable(int level, float iconScale) { + BluetoothBatteryTileIcon(int level, float iconScale) { mLevel = level; mIconScale = iconScale; } @@ -302,15 +298,16 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { for (CachedBluetoothDevice device : devices) { if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue; final Item item = new Item(); - item.icon = R.drawable.ic_qs_bluetooth_on; + item.iconResId = R.drawable.ic_qs_bluetooth_on; item.line1 = device.getName(); item.tag = device; int state = device.getMaxConnectionState(); if (state == BluetoothProfile.STATE_CONNECTED) { - item.icon = R.drawable.ic_qs_bluetooth_connected; + item.iconResId = R.drawable.ic_qs_bluetooth_connected; int batteryLevel = device.getBatteryLevel(); if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { - item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel); + item.icon = new BluetoothBatteryTileIcon(batteryLevel, + 1 /* iconScale */); item.line2 = mContext.getString( R.string.quick_settings_connected_battery_level, Utils.formatPercentage(batteryLevel)); @@ -321,7 +318,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { items.add(connectedDevices, item); connectedDevices++; } else if (state == BluetoothProfile.STATE_CONNECTING) { - item.icon = R.drawable.ic_qs_bluetooth_connecting; + item.iconResId = R.drawable.ic_qs_bluetooth_connecting; item.line2 = mContext.getString(R.string.quick_settings_connecting); items.add(connectedDevices, item); } else { diff --git a/com/android/systemui/qs/tiles/CastTile.java b/com/android/systemui/qs/tiles/CastTile.java index fb396b9d..678aa711 100644 --- a/com/android/systemui/qs/tiles/CastTile.java +++ b/com/android/systemui/qs/tiles/CastTile.java @@ -19,26 +19,17 @@ package com.android.systemui.qs.tiles; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.app.Dialog; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.media.MediaRouter; -import android.os.UserHandle; import android.provider.Settings; import android.service.quicksettings.Tile; import android.util.Log; -import android.view.ContextThemeWrapper; import android.view.View; import android.view.View.OnAttachStateChangeListener; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.Button; -import com.android.internal.app.MediaRouteChooserDialog; -import com.android.internal.app.MediaRouteControllerDialog; import com.android.internal.app.MediaRouteDialogPresenter; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -280,7 +271,7 @@ public class CastTile extends QSTileImpl<BooleanState> { for (CastDevice device : devices) { if (device.state == CastDevice.STATE_CONNECTED) { final Item item = new Item(); - item.icon = R.drawable.ic_qs_cast_on; + item.iconResId = R.drawable.ic_qs_cast_on; item.line1 = getDeviceName(device); item.line2 = mContext.getString(R.string.quick_settings_connected); item.tag = device; @@ -300,7 +291,7 @@ public class CastTile extends QSTileImpl<BooleanState> { final CastDevice device = mVisibleOrder.get(id); if (!devices.contains(device)) continue; final Item item = new Item(); - item.icon = R.drawable.ic_qs_cast_off; + item.iconResId = R.drawable.ic_qs_cast_off; item.line1 = getDeviceName(device); if (device.state == CastDevice.STATE_CONNECTING) { item.line2 = mContext.getString(R.string.quick_settings_connecting); diff --git a/com/android/systemui/qs/tiles/WifiTile.java b/com/android/systemui/qs/tiles/WifiTile.java index 23702736..977a7258 100644 --- a/com/android/systemui/qs/tiles/WifiTile.java +++ b/com/android/systemui/qs/tiles/WifiTile.java @@ -402,7 +402,7 @@ public class WifiTile extends QSTileImpl<SignalState> { final AccessPoint ap = mAccessPoints[i]; final Item item = new Item(); item.tag = ap; - item.icon = mWifiController.getIcon(ap); + item.iconResId = mWifiController.getIcon(ap); item.line1 = ap.getSsid(); item.line2 = ap.isActive() ? ap.getSummary() : null; item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java index 87f24fdb..55ec5e7e 100644 --- a/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -200,25 +200,17 @@ public class SystemServicesProxy { */ public ActivityManager.RunningTaskInfo getRunningTask() { // Note: The set of running tasks from the system is ordered by recency - List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10); - if (tasks == null || tasks.isEmpty()) { - return null; - } - - // Find the first task in a valid stack, we ignore everything from the Recents and PiP - // stacks - for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RunningTaskInfo task = tasks.get(i); - final WindowConfiguration winConfig = task.configuration.windowConfiguration; - if (winConfig.getActivityType() == ACTIVITY_TYPE_RECENTS) { - continue; - } - if (winConfig.getWindowingMode() == WINDOWING_MODE_PINNED) { - continue; + try { + List<ActivityManager.RunningTaskInfo> tasks = mIam.getFilteredTasks(1, + ACTIVITY_TYPE_RECENTS /* ignoreActivityType */, + WINDOWING_MODE_PINNED /* ignoreWindowingMode */); + if (tasks.isEmpty()) { + return null; } - return task; + return tasks.get(0); + } catch (RemoteException e) { + return null; } - return null; } /** diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java index 25c2fc97..7442904e 100644 --- a/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ b/com/android/systemui/recents/views/RecentsTransitionHelper.java @@ -302,11 +302,6 @@ public class RecentsTransitionHelper { */ private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task, final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) { - if (activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_HOME - || windowingMode == WINDOWING_MODE_PINNED) { - return null; - } - // Calculate the offscreen task rect (for tasks that are not backed by views) TaskView taskView = stackView.getChildViewForTask(task); TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm(); diff --git a/com/android/systemui/statusbar/ActivatableNotificationView.java b/com/android/systemui/statusbar/ActivatableNotificationView.java index 68fe9a83..84b7015f 100644 --- a/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -29,7 +29,6 @@ import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewAnimationUtils; -import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -173,12 +172,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private int mOverrideTint; private float mOverrideAmount; private boolean mShadowHidden; - private boolean mWasActivatedOnDown; /** * Similar to mDimmed but is also true if it's not dimmable but should be */ private boolean mNeedsDimming; private int mDimmedAlpha; + private boolean mBlockNextTouch; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); @@ -204,7 +203,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } else { makeInactive(true /* animate */); } - }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); + }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); } @Override @@ -241,9 +240,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mNeedsDimming && !mActivated && ev.getActionMasked() == MotionEvent.ACTION_DOWN + if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN && disallowSingleClick(ev) && !isTouchExplorationEnabled()) { - return true; + if (!mActivated) { + return true; + } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { + mBlockNextTouch = true; + makeInactive(true /* animate */); + return true; + } } return super.onInterceptTouchEvent(ev); } @@ -263,10 +268,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean onTouchEvent(MotionEvent event) { boolean result; - if (event.getAction() == MotionEvent.ACTION_DOWN) { - mWasActivatedOnDown = mActivated; + if (mBlockNextTouch) { + mBlockNextTouch = false; + return false; } - if ((mNeedsDimming && !mActivated) && !isTouchExplorationEnabled() && isInteractive()) { + if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) { boolean wasActivated = mActivated; result = handleTouchEventDimmed(event); if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) { @@ -312,7 +318,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public boolean performClick() { - if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) { + if (!mNeedsDimming || isTouchExplorationEnabled()) { return super.performClick(); } return false; diff --git a/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/com/android/systemui/statusbar/phone/DoubleTapHelper.java index dcb6a380..0d62703c 100644 --- a/com/android/systemui/statusbar/phone/DoubleTapHelper.java +++ b/com/android/systemui/statusbar/phone/DoubleTapHelper.java @@ -142,7 +142,7 @@ public class DoubleTapHelper { && Math.abs(event.getY() - mDownY) < mTouchSlop; } - private boolean isWithinDoubleTapSlop(MotionEvent event) { + public boolean isWithinDoubleTapSlop(MotionEvent event) { if (!mActivated) { // If we're not activated there's no double tap slop to satisfy. return true; diff --git a/com/android/systemui/statusbar/phone/NavigationBarView.java b/com/android/systemui/statusbar/phone/NavigationBarView.java index 9a7039a5..094129c5 100644 --- a/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -448,10 +448,18 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav // Always disable recents when alternate car mode UI is active. boolean disableRecent = mUseCarModeUi - || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); - final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) + || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); + + boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); + if ((disableRecent || disableBack) && inScreenPinning()) { + // Don't hide back and recents buttons when in screen pinning mode, as they are used for + // exiting. + disableBack = false; + disableRecent = false; + } + ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons); if (navButtons != null) { LayoutTransition lt = navButtons.getLayoutTransition(); @@ -461,20 +469,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } } } - if (inLockTask() && disableRecent && !disableHome) { - // Don't hide recents when in lock task, it is used for exiting. - // Unless home is hidden, then in DPM locked mode and no exit available. - disableRecent = false; - } getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); } - private boolean inLockTask() { + private boolean inScreenPinning() { try { - return ActivityManager.getService().isInLockTaskMode(); + return ActivityManager.getService().getLockTaskModeState() + == ActivityManager.LOCK_TASK_MODE_PINNED; } catch (RemoteException e) { return false; } diff --git a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index d24e51c7..40ee8386 100644 --- a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -382,13 +382,6 @@ public class NetworkControllerImpl extends BroadcastReceiver new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... args) { - // Disable tethering if enabling Wifi - final int wifiApState = mWifiManager.getWifiApState(); - if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || - (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { - mWifiManager.setWifiApEnabled(null, false); - } - mWifiManager.setWifiEnabled(enabled); return null; } diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java index 7c9aeded..3d5476d0 100644 --- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java +++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2012 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,24 +16,55 @@ package com.android.uiautomator.testrunner; -import android.app.Instrumentation; +import android.content.Context; import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; -import android.test.InstrumentationTestCase; +import android.view.inputmethod.InputMethodInfo; -import com.android.uiautomator.core.InstrumentationUiAutomatorBridge; +import com.android.internal.view.IInputMethodManager; import com.android.uiautomator.core.UiDevice; +import junit.framework.TestCase; + +import java.util.List; + /** - * UI Automator test case that is executed on the device. + * 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 * @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 InstrumentationTestCase { +public class UiAutomatorTestCase extends TestCase { + 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 @@ -41,7 +72,7 @@ public class UiAutomatorTestCase extends InstrumentationTestCase { * @since API Level 16 */ public UiDevice getUiDevice() { - return UiDevice.getInstance(); + return mUiDevice; } /** @@ -54,43 +85,34 @@ public class UiAutomatorTestCase extends InstrumentationTestCase { 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; } /** - * Initializes this test case. - * - * @param params Instrumentation arguments. + * package private + * @param uiDevice */ - void initialize(Bundle params) { - mParams = params; + void setUiDevice(UiDevice uiDevice) { + mUiDevice = uiDevice; + } - // 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)); - } + /** + * package private + * @param params + */ + void setParams(Bundle params) { + mParams = params; + } - UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge( - getInstrumentation().getContext(), - getInstrumentation().getUiAutomation())); + void setAutomationSupport(IAutomationSupport automationSupport) { + mAutomationSupport = automationSupport; } /** @@ -101,4 +123,28 @@ public class UiAutomatorTestCase extends InstrumentationTestCase { 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. + } } diff --git a/foo/bar/ComplexDao.java b/foo/bar/ComplexDao.java index dbb54fbc..89859e50 100644 --- a/foo/bar/ComplexDao.java +++ b/foo/bar/ComplexDao.java @@ -1,433 +1,69 @@ -package foo.bar; +/* + * 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. + */ -import android.arch.lifecycle.ComputableLiveData; -import android.arch.lifecycle.LiveData; -import android.arch.persistence.room.InvalidationTracker.Observer; -import android.arch.persistence.room.RoomDatabase; -import android.arch.persistence.room.RoomSQLiteQuery; -import android.arch.persistence.room.util.StringUtil; -import android.database.Cursor; -import android.support.annotation.NonNull; -import java.lang.Integer; -import java.lang.Override; -import java.lang.String; -import java.lang.StringBuilder; -import java.util.ArrayList; +package foo.bar; +import android.arch.persistence.room.*; import java.util.List; -import java.util.Set; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class ComplexDao_Impl extends ComplexDao { - private final RoomDatabase __db; - - public ComplexDao_Impl(ComplexDatabase __db) { - super(__db); - this.__db = __db; - } - - @Override - public boolean transactionMethod(int i, String s, long l) { - __db.beginTransaction(); - try { - boolean _result = super.transactionMethod(i, s, l); - __db.setTransactionSuccessful(); - return _result; - } finally { - __db.endTransaction(); - } - } - - @Override - public List<ComplexDao.FullName> fullNames(int id) { - final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); - int _argIndex = 1; - _statement.bindLong(_argIndex, id); - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfFullName = _cursor.getColumnIndexOrThrow("fullName"); - final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id"); - final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final ComplexDao.FullName _item; - _item = new ComplexDao.FullName(); - _item.fullName = _cursor.getString(_cursorIndexOfFullName); - _item.id = _cursor.getInt(_cursorIndexOfId); - _result.add(_item); - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } +import android.arch.lifecycle.LiveData; +@Dao +abstract class ComplexDao { + static class FullName { + public int id; + public String fullName; } - @Override - public User getById(int id) { - final String _sql = "SELECT * FROM user where uid = ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); - int _argIndex = 1; - _statement.bindLong(_argIndex, id); - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final User _result; - if(_cursor.moveToFirst()) { - _result = new User(); - _result.uid = _cursor.getInt(_cursorIndexOfUid); - _result.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _result.setLastName(_tmpLastName); - _result.age = _cursor.getInt(_cursorIndexOfAge); - } else { - _result = null; - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + private final ComplexDatabase mDb; - @Override - public User findByName(String name, String lastName) { - final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2); - int _argIndex = 1; - if (name == null) { - _statement.bindNull(_argIndex); - } else { - _statement.bindString(_argIndex, name); - } - _argIndex = 2; - if (lastName == null) { - _statement.bindNull(_argIndex); - } else { - _statement.bindString(_argIndex, lastName); - } - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final User _result; - if(_cursor.moveToFirst()) { - _result = new User(); - _result.uid = _cursor.getInt(_cursorIndexOfUid); - _result.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _result.setLastName(_tmpLastName); - _result.age = _cursor.getInt(_cursorIndexOfAge); - } else { - _result = null; - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } + public ComplexDao(ComplexDatabase db) { + mDb = db; } - @Override - public List<User> loadAllByIds(int... ids) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT * FROM user where uid IN ("); - final int _inputSize = ids.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (int _item : ids) { - _statement.bindLong(_argIndex, _item); - _argIndex ++; - } - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final List<User> _result = new ArrayList<User>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final User _item_1; - _item_1 = new User(); - _item_1.uid = _cursor.getInt(_cursorIndexOfUid); - _item_1.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _item_1.setLastName(_tmpLastName); - _item_1.age = _cursor.getInt(_cursorIndexOfAge); - _result.add(_item_1); - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } + @Transaction + public boolean transactionMethod(int i, String s, long l) { + return true; } - @Override - int getAge(int id) { - final String _sql = "SELECT ageColumn FROM user where uid = ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); - int _argIndex = 1; - _statement.bindLong(_argIndex, id); - final Cursor _cursor = __db.query(_statement); - try { - final int _result; - if(_cursor.moveToFirst()) { - _result = _cursor.getInt(0); - } else { - _result = 0; - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + @Query("SELECT name || lastName as fullName, uid as id FROM user where uid = :id") + abstract public List<FullName> fullNames(int id); - @Override - public int[] getAllAges(int... ids) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT ageColumn FROM user where uid IN("); - final int _inputSize = ids.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (int _item : ids) { - _statement.bindLong(_argIndex, _item); - _argIndex ++; - } - final Cursor _cursor = __db.query(_statement); - try { - final int[] _result = new int[_cursor.getCount()]; - int _index = 0; - while(_cursor.moveToNext()) { - final int _item_1; - _item_1 = _cursor.getInt(0); - _result[_index] = _item_1; - _index ++; - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + @Query("SELECT * FROM user where uid = :id") + abstract public User getById(int id); - @Override - public List<Integer> getAllAgesAsList(List<Integer> ids) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT ageColumn FROM user where uid IN("); - final int _inputSize = ids.size(); - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (Integer _item : ids) { - if (_item == null) { - _statement.bindNull(_argIndex); - } else { - _statement.bindLong(_argIndex, _item); - } - _argIndex ++; - } - final Cursor _cursor = __db.query(_statement); - try { - final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final Integer _item_1; - if (_cursor.isNull(0)) { - _item_1 = null; - } else { - _item_1 = _cursor.getInt(0); - } - _result.add(_item_1); - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + @Query("SELECT * FROM user where name LIKE :name AND lastName LIKE :lastName") + abstract public User findByName(String name, String lastName); - @Override - public LiveData<User> getByIdLive(int id) { - final String _sql = "SELECT * FROM user where uid = ?"; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1); - int _argIndex = 1; - _statement.bindLong(_argIndex, id); - return new ComputableLiveData<User>() { - private Observer _observer; + @Query("SELECT * FROM user where uid IN (:ids)") + abstract public List<User> loadAllByIds(int... ids); - @Override - protected User compute() { - if (_observer == null) { - _observer = new Observer("user") { - @Override - public void onInvalidated(@NonNull Set<String> tables) { - invalidate(); - } - }; - __db.getInvalidationTracker().addWeakObserver(_observer); - } - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final User _result; - if(_cursor.moveToFirst()) { - _result = new User(); - _result.uid = _cursor.getInt(_cursorIndexOfUid); - _result.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _result.setLastName(_tmpLastName); - _result.age = _cursor.getInt(_cursorIndexOfAge); - } else { - _result = null; - } - return _result; - } finally { - _cursor.close(); - } - } + @Query("SELECT ageColumn FROM user where uid = :id") + abstract int getAge(int id); - @Override - protected void finalize() { - _statement.release(); - } - }.getLiveData(); - } + @Query("SELECT ageColumn FROM user where uid IN(:ids)") + abstract public int[] getAllAges(int... ids); - @Override - public LiveData<List<User>> loadUsersByIdsLive(int... ids) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT * FROM user where uid IN ("); - final int _inputSize = ids.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (int _item : ids) { - _statement.bindLong(_argIndex, _item); - _argIndex ++; - } - return new ComputableLiveData<List<User>>() { - private Observer _observer; + @Query("SELECT ageColumn FROM user where uid IN(:ids)") + abstract public List<Integer> getAllAgesAsList(List<Integer> ids); - @Override - protected List<User> compute() { - if (_observer == null) { - _observer = new Observer("user") { - @Override - public void onInvalidated(@NonNull Set<String> tables) { - invalidate(); - } - }; - __db.getInvalidationTracker().addWeakObserver(_observer); - } - final Cursor _cursor = __db.query(_statement); - try { - final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); - final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name"); - final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName"); - final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn"); - final List<User> _result = new ArrayList<User>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final User _item_1; - _item_1 = new User(); - _item_1.uid = _cursor.getInt(_cursorIndexOfUid); - _item_1.name = _cursor.getString(_cursorIndexOfName); - final String _tmpLastName; - _tmpLastName = _cursor.getString(_cursorIndexOfLastName); - _item_1.setLastName(_tmpLastName); - _item_1.age = _cursor.getInt(_cursorIndexOfAge); - _result.add(_item_1); - } - return _result; - } finally { - _cursor.close(); - } - } + @Query("SELECT * FROM user where uid = :id") + abstract public LiveData<User> getByIdLive(int id); - @Override - protected void finalize() { - _statement.release(); - } - }.getLiveData(); - } + @Query("SELECT * FROM user where uid IN (:ids)") + abstract public LiveData<List<User>> loadUsersByIdsLive(int... ids); - @Override - public List<Integer> getAllAgesAsList(List<Integer> ids1, int[] ids2, int... ids3) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("SELECT ageColumn FROM user where uid IN("); - final int _inputSize = ids1.size(); - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(") OR uid IN ("); - final int _inputSize_1 = ids2.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1); - _stringBuilder.append(") OR uid IN ("); - final int _inputSize_2 = ids3.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - final int _argCount = 0 + _inputSize + _inputSize_1 + _inputSize_2; - final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount); - int _argIndex = 1; - for (Integer _item : ids1) { - if (_item == null) { - _statement.bindNull(_argIndex); - } else { - _statement.bindLong(_argIndex, _item); - } - _argIndex ++; - } - _argIndex = 1 + _inputSize; - for (int _item_1 : ids2) { - _statement.bindLong(_argIndex, _item_1); - _argIndex ++; - } - _argIndex = 1 + _inputSize + _inputSize_1; - for (int _item_2 : ids3) { - _statement.bindLong(_argIndex, _item_2); - _argIndex ++; - } - final Cursor _cursor = __db.query(_statement); - try { - final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount()); - while(_cursor.moveToNext()) { - final Integer _item_3; - if (_cursor.isNull(0)) { - _item_3 = null; - } else { - _item_3 = _cursor.getInt(0); - } - _result.add(_item_3); - } - return _result; - } finally { - _cursor.close(); - _statement.release(); - } - } + @Query("SELECT ageColumn FROM user where uid IN(:ids1) OR uid IN (:ids2) OR uid IN (:ids3)") + abstract public List<Integer> getAllAgesAsList(List<Integer> ids1, + int[] ids2, int... ids3); } diff --git a/foo/bar/ComplexDatabase.java b/foo/bar/ComplexDatabase.java index cfdc1101..f35e0b8a 100644 --- a/foo/bar/ComplexDatabase.java +++ b/foo/bar/ComplexDatabase.java @@ -1,99 +1,23 @@ -package foo.bar; - -import android.arch.persistence.db.SupportSQLiteDatabase; -import android.arch.persistence.db.SupportSQLiteOpenHelper; -import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback; -import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration; -import android.arch.persistence.room.DatabaseConfiguration; -import android.arch.persistence.room.InvalidationTracker; -import android.arch.persistence.room.RoomOpenHelper; -import android.arch.persistence.room.RoomOpenHelper.Delegate; -import android.arch.persistence.room.util.TableInfo; -import android.arch.persistence.room.util.TableInfo.Column; -import android.arch.persistence.room.util.TableInfo.ForeignKey; -import android.arch.persistence.room.util.TableInfo.Index; -import java.lang.IllegalStateException; -import java.lang.Override; -import java.lang.String; -import java.util.HashMap; -import java.util.HashSet; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class ComplexDatabase_Impl extends ComplexDatabase { - private volatile ComplexDao _complexDao; - - protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) { - final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1923) { - public void createAllTables(SupportSQLiteDatabase _db) { - _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`uid` INTEGER NOT NULL, `name` TEXT, `lastName` TEXT, `ageColumn` INTEGER NOT NULL, PRIMARY KEY(`uid`))"); - _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)"); - _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6773601c5bcf94c71ee4eb0de04f21a4\")"); - } - - public void dropAllTables(SupportSQLiteDatabase _db) { - _db.execSQL("DROP TABLE IF EXISTS `User`"); - } +/* + * 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. + */ - protected void onCreate(SupportSQLiteDatabase _db) { - if (mCallbacks != null) { - for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) { - mCallbacks.get(_i).onCreate(_db); - } - } - } - - public void onOpen(SupportSQLiteDatabase _db) { - mDatabase = _db; - internalInitInvalidationTracker(_db); - if (mCallbacks != null) { - for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) { - mCallbacks.get(_i).onOpen(_db); - } - } - } - - protected void validateMigration(SupportSQLiteDatabase _db) { - final HashMap<String, TableInfo.Column> _columnsUser = new HashMap<String, TableInfo.Column>(4); - _columnsUser.put("uid", new TableInfo.Column("uid", "INTEGER", true, 1)); - _columnsUser.put("name", new TableInfo.Column("name", "TEXT", false, 0)); - _columnsUser.put("lastName", new TableInfo.Column("lastName", "TEXT", false, 0)); - _columnsUser.put("ageColumn", new TableInfo.Column("ageColumn", "INTEGER", true, 0)); - final HashSet<TableInfo.ForeignKey> _foreignKeysUser = new HashSet<TableInfo.ForeignKey>(0); - final HashSet<TableInfo.Index> _indicesUser = new HashSet<TableInfo.Index>(0); - final TableInfo _infoUser = new TableInfo("User", _columnsUser, _foreignKeysUser, _indicesUser); - final TableInfo _existingUser = TableInfo.read(_db, "User"); - if (! _infoUser.equals(_existingUser)) { - throw new IllegalStateException("Migration didn't properly handle User(foo.bar.User).\n" - + " Expected:\n" + _infoUser + "\n" - + " Found:\n" + _existingUser); - } - } - }, "6773601c5bcf94c71ee4eb0de04f21a4"); - final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context) - .name(configuration.name) - .callback(_openCallback) - .build(); - final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig); - return _helper; - } - - @Override - protected InvalidationTracker createInvalidationTracker() { - return new InvalidationTracker(this, "User"); - } - - @Override - ComplexDao getComplexDao() { - if (_complexDao != null) { - return _complexDao; - } else { - synchronized(this) { - if(_complexDao == null) { - _complexDao = new ComplexDao_Impl(this); - } - return _complexDao; - } - } - } +package foo.bar; +import android.arch.persistence.room.*; +import java.util.List; +@Database(entities = {User.class}, version = 1923) +abstract class ComplexDatabase extends RoomDatabase { + abstract ComplexDao getComplexDao(); } diff --git a/foo/bar/DeletionDao.java b/foo/bar/DeletionDao.java index 067bf670..997f2906 100644 --- a/foo/bar/DeletionDao.java +++ b/foo/bar/DeletionDao.java @@ -1,240 +1,51 @@ -package foo.bar; +/* + * 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. + */ -import android.arch.persistence.db.SupportSQLiteStatement; -import android.arch.persistence.room.EntityDeletionOrUpdateAdapter; -import android.arch.persistence.room.RoomDatabase; -import android.arch.persistence.room.SharedSQLiteStatement; -import android.arch.persistence.room.util.StringUtil; -import java.lang.Override; -import java.lang.String; -import java.lang.StringBuilder; +package foo.bar; +import android.arch.persistence.room.*; import java.util.List; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class DeletionDao_Impl implements DeletionDao { - private final RoomDatabase __db; - - private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser; - - private final EntityDeletionOrUpdateAdapter __deletionAdapterOfMultiPKeyEntity; - - private final EntityDeletionOrUpdateAdapter __deletionAdapterOfBook; - - private final SharedSQLiteStatement __preparedStmtOfDeleteByUid; - - private final SharedSQLiteStatement __preparedStmtOfDeleteEverything; - - public DeletionDao_Impl(RoomDatabase __db) { - this.__db = __db; - this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) { - @Override - public String createQuery() { - return "DELETE FROM `User` WHERE `uid` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, User value) { - stmt.bindLong(1, value.uid); - } - }; - this.__deletionAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) { - @Override - public String createQuery() { - return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) { - if (value.name == null) { - stmt.bindNull(1); - } else { - stmt.bindString(1, value.name); - } - if (value.lastName == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.lastName); - } - } - }; - this.__deletionAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) { - @Override - public String createQuery() { - return "DELETE FROM `Book` WHERE `bookId` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, Book value) { - stmt.bindLong(1, value.bookId); - } - }; - this.__preparedStmtOfDeleteByUid = new SharedSQLiteStatement(__db) { - @Override - public String createQuery() { - final String _query = "DELETE FROM user where uid = ?"; - return _query; - } - }; - this.__preparedStmtOfDeleteEverything = new SharedSQLiteStatement(__db) { - @Override - public String createQuery() { - final String _query = "DELETE FROM user"; - return _query; - } - }; - } - - @Override - public void deleteUser(User user) { - __db.beginTransaction(); - try { - __deletionAdapterOfUser.handle(user); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void deleteUsers(User user1, List<User> others) { - __db.beginTransaction(); - try { - __deletionAdapterOfUser.handle(user1); - __deletionAdapterOfUser.handleMultiple(others); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void deleteArrayOfUsers(User[] users) { - __db.beginTransaction(); - try { - __deletionAdapterOfUser.handleMultiple(users); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public int deleteUserAndReturnCount(User user) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__deletionAdapterOfUser.handle(user); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - @Override - public int deleteUserAndReturnCount(User user1, List<User> others) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__deletionAdapterOfUser.handle(user1); - _total +=__deletionAdapterOfUser.handleMultiple(others); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } +@Dao +abstract interface DeletionDao { + @Delete + void deleteUser(User user); + @Delete + void deleteUsers(User user1, List<User> others); + @Delete + void deleteArrayOfUsers(User[] users); - @Override - public int deleteUserAndReturnCount(User[] users) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__deletionAdapterOfUser.handleMultiple(users); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } + @Delete + int deleteUserAndReturnCount(User user); + @Delete + int deleteUserAndReturnCount(User user1, List<User> others); + @Delete + int deleteUserAndReturnCount(User[] users); - @Override - public int multiPKey(MultiPKeyEntity entity) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__deletionAdapterOfMultiPKeyEntity.handle(entity); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } + @Delete + int multiPKey(MultiPKeyEntity entity); - @Override - public void deleteUserAndBook(User user, Book book) { - __db.beginTransaction(); - try { - __deletionAdapterOfUser.handle(user); - __deletionAdapterOfBook.handle(book); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } + @Query("DELETE FROM user where uid = :uid") + int deleteByUid(int uid); - @Override - public int deleteByUid(int uid) { - final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire(); - __db.beginTransaction(); - try { - int _argIndex = 1; - _stmt.bindLong(_argIndex, uid); - final int _result = _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - return _result; - } finally { - __db.endTransaction(); - __preparedStmtOfDeleteByUid.release(_stmt); - } - } + @Query("DELETE FROM user where uid IN(:uid)") + int deleteByUidList(int... uid); - @Override - public int deleteEverything() { - final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteEverything.acquire(); - __db.beginTransaction(); - try { - final int _result = _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - return _result; - } finally { - __db.endTransaction(); - __preparedStmtOfDeleteEverything.release(_stmt); - } - } + @Delete + void deleteUserAndBook(User user, Book book); - @Override - public int deleteByUidList(int... uid) { - StringBuilder _stringBuilder = StringUtil.newStringBuilder(); - _stringBuilder.append("DELETE FROM user where uid IN("); - final int _inputSize = uid.length; - StringUtil.appendPlaceholders(_stringBuilder, _inputSize); - _stringBuilder.append(")"); - final String _sql = _stringBuilder.toString(); - SupportSQLiteStatement _stmt = __db.compileStatement(_sql); - int _argIndex = 1; - for (int _item : uid) { - _stmt.bindLong(_argIndex, _item); - _argIndex ++; - } - __db.beginTransaction(); - try { - final int _result = _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - return _result; - } finally { - __db.endTransaction(); - } - } + @Query("DELETE FROM user") + int deleteEverything(); } diff --git a/foo/bar/UpdateDao.java b/foo/bar/UpdateDao.java index 1190a0df..040b5c79 100644 --- a/foo/bar/UpdateDao.java +++ b/foo/bar/UpdateDao.java @@ -1,240 +1,48 @@ -package foo.bar; +/* + * 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. + */ -import android.arch.persistence.db.SupportSQLiteStatement; -import android.arch.persistence.room.EntityDeletionOrUpdateAdapter; -import android.arch.persistence.room.RoomDatabase; -import android.arch.persistence.room.SharedSQLiteStatement; -import java.lang.Override; -import java.lang.String; +package foo.bar; +import android.arch.persistence.room.*; import java.util.List; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class UpdateDao_Impl implements UpdateDao { - private final RoomDatabase __db; - - private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser; - - private final EntityDeletionOrUpdateAdapter __updateAdapterOfMultiPKeyEntity; - - private final EntityDeletionOrUpdateAdapter __updateAdapterOfBook; - - private final SharedSQLiteStatement __preparedStmtOfAgeUserByUid; - - private final SharedSQLiteStatement __preparedStmtOfAgeUserAll; - - public UpdateDao_Impl(RoomDatabase __db) { - this.__db = __db; - this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) { - @Override - public String createQuery() { - return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, User value) { - stmt.bindLong(1, value.uid); - if (value.name == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.name); - } - if (value.getLastName() == null) { - stmt.bindNull(3); - } else { - stmt.bindString(3, value.getLastName()); - } - stmt.bindLong(4, value.age); - stmt.bindLong(5, value.uid); - } - }; - this.__updateAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) { - @Override - public String createQuery() { - return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) { - if (value.name == null) { - stmt.bindNull(1); - } else { - stmt.bindString(1, value.name); - } - if (value.lastName == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.lastName); - } - if (value.name == null) { - stmt.bindNull(3); - } else { - stmt.bindString(3, value.name); - } - if (value.lastName == null) { - stmt.bindNull(4); - } else { - stmt.bindString(4, value.lastName); - } - } - }; - this.__updateAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) { - @Override - public String createQuery() { - return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, Book value) { - stmt.bindLong(1, value.bookId); - stmt.bindLong(2, value.uid); - stmt.bindLong(3, value.bookId); - } - }; - this.__preparedStmtOfAgeUserByUid = new SharedSQLiteStatement(__db) { - @Override - public String createQuery() { - final String _query = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?"; - return _query; - } - }; - this.__preparedStmtOfAgeUserAll = new SharedSQLiteStatement(__db) { - @Override - public String createQuery() { - final String _query = "UPDATE User SET ageColumn = ageColumn + 1"; - return _query; - } - }; - } - - @Override - public void updateUser(User user) { - __db.beginTransaction(); - try { - __updateAdapterOfUser.handle(user); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void updateUsers(User user1, List<User> others) { - __db.beginTransaction(); - try { - __updateAdapterOfUser.handle(user1); - __updateAdapterOfUser.handleMultiple(others); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void updateArrayOfUsers(User[] users) { - __db.beginTransaction(); - try { - __updateAdapterOfUser.handleMultiple(users); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public int updateUserAndReturnCount(User user) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__updateAdapterOfUser.handle(user); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - - @Override - public int updateUserAndReturnCount(User user1, List<User> others) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__updateAdapterOfUser.handle(user1); - _total +=__updateAdapterOfUser.handleMultiple(others); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - - @Override - public int updateUserAndReturnCount(User[] users) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__updateAdapterOfUser.handleMultiple(users); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - - @Override - public int multiPKey(MultiPKeyEntity entity) { - int _total = 0; - __db.beginTransaction(); - try { - _total +=__updateAdapterOfMultiPKeyEntity.handle(entity); - __db.setTransactionSuccessful(); - return _total; - } finally { - __db.endTransaction(); - } - } - - @Override - public void updateUserAndBook(User user, Book book) { - __db.beginTransaction(); - try { - __updateAdapterOfUser.handle(user); - __updateAdapterOfBook.handle(book); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void ageUserByUid(String uid) { - final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserByUid.acquire(); - __db.beginTransaction(); - try { - int _argIndex = 1; - if (uid == null) { - _stmt.bindNull(_argIndex); - } else { - _stmt.bindString(_argIndex, uid); - } - _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - __preparedStmtOfAgeUserByUid.release(_stmt); - } - } - @Override - public void ageUserAll() { - final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire(); - __db.beginTransaction(); - try { - _stmt.executeUpdateDelete(); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - __preparedStmtOfAgeUserAll.release(_stmt); - } - } +@Dao +abstract interface UpdateDao { + @Update + void updateUser(User user); + @Update + void updateUsers(User user1, List<User> others); + @Update + void updateArrayOfUsers(User[] users); + + @Update + int updateUserAndReturnCount(User user); + @Update + int updateUserAndReturnCount(User user1, List<User> others); + @Update + int updateUserAndReturnCount(User[] users); + + @Update + int multiPKey(MultiPKeyEntity entity); + + @Update + void updateUserAndBook(User user, Book book); + + @Query("UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = :uid") + void ageUserByUid(String uid); + + @Query("UPDATE User SET ageColumn = ageColumn + 1") + void ageUserAll(); } diff --git a/foo/bar/WriterDao.java b/foo/bar/WriterDao.java index cfad0469..e122479b 100644 --- a/foo/bar/WriterDao.java +++ b/foo/bar/WriterDao.java @@ -15,131 +15,17 @@ */ package foo.bar; - -import android.arch.persistence.db.SupportSQLiteStatement; -import android.arch.persistence.room.EntityInsertionAdapter; -import android.arch.persistence.room.RoomDatabase; - -import java.lang.Override; -import java.lang.String; +import android.arch.persistence.room.*; import java.util.List; -import javax.annotation.Generated; - -@Generated("android.arch.persistence.room.RoomProcessor") -public class WriterDao_Impl implements WriterDao { - private final RoomDatabase __db; - - private final EntityInsertionAdapter __insertionAdapterOfUser; - - private final EntityInsertionAdapter __insertionAdapterOfUser_1; - - private final EntityInsertionAdapter __insertionAdapterOfBook; - - public WriterDao_Impl(RoomDatabase __db) { - this.__db = __db; - this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) { - @Override - public String createQuery() { - return "INSERT OR ABORT INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES" - + " (?,?,?,?)"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, User value) { - stmt.bindLong(1, value.uid); - if (value.name == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.name); - } - if (value.getLastName() == null) { - stmt.bindNull(3); - } else { - stmt.bindString(3, value.getLastName()); - } - stmt.bindLong(4, value.age); - } - }; - this.__insertionAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) { - @Override - public String createQuery() { - return "INSERT OR REPLACE INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES" - + " (?,?,?,?)"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, User value) { - stmt.bindLong(1, value.uid); - if (value.name == null) { - stmt.bindNull(2); - } else { - stmt.bindString(2, value.name); - } - if (value.getLastName() == null) { - stmt.bindNull(3); - } else { - stmt.bindString(3, value.getLastName()); - } - stmt.bindLong(4, value.age); - } - }; - this.__insertionAdapterOfBook = new EntityInsertionAdapter<Book>(__db) { - @Override - public String createQuery() { - return "INSERT OR ABORT INTO `Book`(`bookId`,`uid`) VALUES (?,?)"; - } - - @Override - public void bind(SupportSQLiteStatement stmt, Book value) { - stmt.bindLong(1, value.bookId); - stmt.bindLong(2, value.uid); - } - }; - } - - @Override - public void insertUser(User user) { - __db.beginTransaction(); - try { - __insertionAdapterOfUser.insert(user); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void insertUsers(User user1, List<User> others) { - __db.beginTransaction(); - try { - __insertionAdapterOfUser.insert(user1); - __insertionAdapterOfUser.insert(others); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - - @Override - public void insertUsers(User[] users) { - __db.beginTransaction(); - try { - __insertionAdapterOfUser_1.insert(users); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } - @Override - public void insertUserAndBook(User user, Book book) { - __db.beginTransaction(); - try { - __insertionAdapterOfUser.insert(user); - __insertionAdapterOfBook.insert(book); - __db.setTransactionSuccessful(); - } finally { - __db.endTransaction(); - } - } +@Dao +abstract interface WriterDao { + @Insert + void insertUser(User user); + @Insert + void insertUsers(User user1, List<User> others); + @Insert(onConflict=OnConflictStrategy.REPLACE) + void insertUsers(User[] users); + @Insert + void insertUserAndBook(User user, Book book); } diff --git a/java/io/RandomAccessFile.java b/java/io/RandomAccessFile.java index df607cb6..985f8ed6 100644 --- a/java/io/RandomAccessFile.java +++ b/java/io/RandomAccessFile.java @@ -28,6 +28,7 @@ package java.io; import java.nio.channels.FileChannel; import sun.nio.ch.FileChannelImpl; +import android.system.Os; import android.system.ErrnoException; import dalvik.system.CloseGuard; import libcore.io.IoBridge; @@ -68,6 +69,12 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { // BEGIN Android-added: CloseGuard and some helper fields for Android changes in this file. private final CloseGuard guard = CloseGuard.get(); private final byte[] scratch = new byte[8]; + + private static final int FLUSH_NONE = 0; + private static final int FLUSH_FSYNC = 1; + private static final int FLUSH_FDATASYNC = 2; + private int flushAfterWrite = FLUSH_NONE; + private int mode; // END Android-added: CloseGuard and some helper fields for Android changes in this file. @@ -230,9 +237,17 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { rw = true; if (mode.length() > 2) { if (mode.equals("rws")) { - imode |= O_SYNC; + // Android-changed: For performance reasons, use fsync after each write. + // RandomAccessFile.write may result in multiple write syscalls, + // O_SYNC/O_DSYNC flags will cause a blocking wait on each syscall. Replacing + // them with single fsync/fdatasync call gives better performance with only + // minor decrease in reliability. + // imode |= O_SYNC; + flushAfterWrite = FLUSH_FSYNC; } else if (mode.equals("rwd")) { - imode |= O_DSYNC; + // Android-changed: For performance reasons, use fdatasync after each write. + // imode |= O_DSYNC; + flushAfterWrite = FLUSH_FDATASYNC; } else { imode = -1; } @@ -267,10 +282,29 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { // BEGIN Android-changed: Use IoBridge.open() instead of open. fd = IoBridge.open(name, imode); + maybeSync(); guard.open("close"); // END Android-changed: Use IoBridge.open() instead of open. } + // BEGIN Android-added: Sync after rws/rwd write + private void maybeSync() { + if (flushAfterWrite == FLUSH_FSYNC) { + try { + fd.sync(); + } catch (IOException e) { + // Ignored + } + } else if (flushAfterWrite == FLUSH_FDATASYNC) { + try { + Os.fdatasync(fd); + } catch (ErrnoException e) { + // Ignored + } + } + } + // END Android-added: Sync after rws/rwd write + /** * Returns the opaque file descriptor object associated with this * stream. @@ -329,7 +363,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * end-of-file has been reached. */ public int read() throws IOException { - // Android-changed: Implement on top of low-level API, not directly natively. + // Android-changed: Implement on top of libcore os API. // return read0(); return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1; } @@ -342,7 +376,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @exception IOException If an I/O error has occurred. */ private int readBytes(byte b[], int off, int len) throws IOException { - // Android-changed: Implement on top of low-level API, not directly natively. + // Android-changed: Implement on top of libcore os API. ioTracker.trackIo(len, IoTracker.Mode.READ); return IoBridge.read(fd, b, off, len); } @@ -485,10 +519,11 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @exception IOException if an I/O error occurs. */ public void write(int b) throws IOException { - // Android-changed: Implement on top of low-level API, not directly natively. + // BEGIN Android-changed: Implement on top of libcore os API. // write0(b); scratch[0] = (byte) (b & 0xff); write(scratch, 0, 1); + // END Android-changed: Implement on top of libcore os API. } /** @@ -500,9 +535,11 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @exception IOException If an I/O error has occurred. */ private void writeBytes(byte b[], int off, int len) throws IOException { - // Android-changed: Implement on top of low-level API, not directly natively. + // BEGIN Android-changed: Implement on top of libcore os API. ioTracker.trackIo(len, IoTracker.Mode.WRITE); IoBridge.write(fd, b, off, len); + maybeSync(); + // END Android-changed: Implement on top of libcore os API. } /** @@ -539,12 +576,13 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @exception IOException if an I/O error occurs. */ public long getFilePointer() throws IOException { - // Android-changed: Implement on top of low-level API, not directly natively. + // BEGIN Android-changed: Implement on top of libcore os API. try { return Libcore.os.lseek(fd, 0L, SEEK_CUR); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } + // END Android-changed: Implement on top of libcore os API. } /** @@ -567,7 +605,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { // throw new IOException("Negative seek offset"); throw new IOException("offset < 0: " + pos); } else { - // Android-changed: Implement on top of low-level API, not directly natively. + // BEGIN Android-changed: Implement on top of libcore os API. // seek0(pos); try { Libcore.os.lseek(fd, pos, SEEK_SET); @@ -575,6 +613,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } + // END Android-changed: Implement on top of libcore os API. } } @@ -585,12 +624,13 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @exception IOException if an I/O error occurs. */ public long length() throws IOException { - // Android-changed: Implement on top of low-level API, not directly natively. + // BEGIN Android-changed: Implement on top of libcore os API. try { return Libcore.os.fstat(fd).st_size; } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } + // END Android-changed: Implement on top of libcore os API. } /** @@ -613,7 +653,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @since 1.2 */ public void setLength(long newLength) throws IOException { - // BEGIN Android-changed: Implement on top of low-level API, not directly natively. + // BEGIN Android-changed: Implement on top of libcore os API. if (newLength < 0) { throw new IllegalArgumentException("newLength < 0"); } @@ -627,7 +667,8 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { if (filePointer > newLength) { seek(newLength); } - // END Android-changed: Implement on top of low-level API, not directly natively. + maybeSync(); + // END Android-changed: Implement on top of libcore os API. } @@ -654,12 +695,12 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { closed = true; } - // BEGIN Android-changed: Implement on top of low-level API, not directly natively. + // BEGIN Android-changed: Implement on top of libcore os API. if (channel != null && channel.isOpen()) { channel.close(); } IoBridge.closeAndSignalBlockedThreads(fd); - // END Android-changed: Implement on top of low-level API, not directly natively. + // END Android-changed: Implement on top of libcore os API. } // diff --git a/java/net/AbstractPlainSocketImpl.java b/java/net/AbstractPlainSocketImpl.java index 85eac6fc..e5b03010 100644 --- a/java/net/AbstractPlainSocketImpl.java +++ b/java/net/AbstractPlainSocketImpl.java @@ -557,7 +557,6 @@ abstract class AbstractPlainSocketImpl extends SocketImpl // Also, close the CloseGuard when the #close is called. if (!closePending) { closePending = true; - SocketTagger.get().untag(fd); guard.close(); if (fdUseCount == 0) { diff --git a/java/text/DecimalFormatSymbols.java b/java/text/DecimalFormatSymbols.java index a9f11c8c..59b8cdc6 100644 --- a/java/text/DecimalFormatSymbols.java +++ b/java/text/DecimalFormatSymbols.java @@ -660,12 +660,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { values[0] = String.valueOf(localeData.decimalSeparator); values[1] = String.valueOf(localeData.groupingSeparator); values[2] = String.valueOf(localeData.patternSeparator); - values[3] = String.valueOf(localeData.percent); + values[3] = localeData.percent; values[4] = String.valueOf(localeData.zeroDigit); values[5] = "#"; values[6] = localeData.minusSign; values[7] = localeData.exponentSeparator; - values[8] = String.valueOf(localeData.perMill); + values[8] = localeData.perMill; values[9] = localeData.infinity; values[10] = localeData.NaN; data[0] = values; diff --git a/javax/crypto/Cipher.java b/javax/crypto/Cipher.java index e3c266f4..010587d4 100644 --- a/javax/crypto/Cipher.java +++ b/javax/crypto/Cipher.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2014 The Android Open Source Project - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -227,6 +227,18 @@ import sun.security.jca.*; public class Cipher { + // Android-note: Android reimplements provider selection. + // + // Android uses different provider/impl selection code than upstream does. Provider + // selection permeates much of this class, so this class is forked significantly + // from the upstream version. Not every change is marked, and any changes to upstream code + // should be evaluated to see if they should be merged. + // + // The changes are chiefly in construction (constructors, getInstance, and createCipher) and + // initialization (init and chooseProvider). Most of the actual implementation is in the + // classes and methods at the bottom of this file. + + // Android-removed: this debugging mechanism is not used in Android. /* private static final Debug debug = @@ -280,11 +292,22 @@ public class Cipher { private CipherSpi spi; // The transformation + // Android-changed: Made final. final private String transformation; + // Android-added: Added tokenizedTransformation. // The tokenized version of transformation final private String[] tokenizedTransformation; + // Android-removed: Removed cryptoPerm. + /* + // Crypto permission representing the maximum allowable cryptographic + // strength that this Cipher object can be used for. (The cryptographic + // strength is a function of the keysize and algorithm parameters encoded + // in the crypto permission.) + private CryptoPermission cryptoPerm; + */ + // The exemption mechanism that needs to be enforced private ExemptionMechanism exmech; @@ -298,8 +321,28 @@ public class Cipher { // The OID for the KeyUsage extension in an X.509 v3 certificate private static final String KEY_USAGE_EXTENSION_OID = "2.5.29.15"; + // BEGIN Android-changed: Reimplement provider selection. + // See note at top of class. private final SpiAndProviderUpdater spiAndProviderUpdater; + /* + // next SPI to try in provider selection + // null once provider is selected + private CipherSpi firstSpi; + // next service to try in provider selection + // null once provider is selected + private Service firstService; + + // remaining services to try in provider selection + // null once provider is selected + private Iterator<Service> serviceIterator; + + // list of transform Strings to lookup in the provider + private List<Transform> transforms; + + private final Object lock; + */ + // END Android-changed: Reimplement provider selection. /** * Creates a Cipher object. @@ -376,6 +419,141 @@ public class Cipher { return parts; } + // BEGIN Android-removed: Reimplement provider selection. + // See note at top of class. + /* + // Provider attribute name for supported chaining mode + private final static String ATTR_MODE = "SupportedModes"; + // Provider attribute name for supported padding names + private final static String ATTR_PAD = "SupportedPaddings"; + + // constants indicating whether the provider supports + // a given mode or padding + private final static int S_NO = 0; // does not support + private final static int S_MAYBE = 1; // unable to determine + private final static int S_YES = 2; // does support + + /** + * Nested class to deal with modes and paddings. + * + private static class Transform { + // transform string to lookup in the provider + final String transform; + // the mode/padding suffix in upper case. for example, if the algorithm + // to lookup is "DES/CBC/PKCS5Padding" suffix is "/CBC/PKCS5PADDING" + // if loopup is "DES", suffix is the empty string + // needed because aliases prevent straight transform.equals() + final String suffix; + // value to pass to setMode() or null if no such call required + final String mode; + // value to pass to setPadding() or null if no such call required + final String pad; + Transform(String alg, String suffix, String mode, String pad) { + this.transform = alg + suffix; + this.suffix = suffix.toUpperCase(Locale.ENGLISH); + this.mode = mode; + this.pad = pad; + } + // set mode and padding for the given SPI + void setModePadding(CipherSpi spi) throws NoSuchAlgorithmException, + NoSuchPaddingException { + if (mode != null) { + spi.engineSetMode(mode); + } + if (pad != null) { + spi.engineSetPadding(pad); + } + } + // check whether the given services supports the mode and + // padding described by this Transform + int supportsModePadding(Service s) { + int smode = supportsMode(s); + if (smode == S_NO) { + return smode; + } + int spad = supportsPadding(s); + // our constants are defined so that Math.min() is a tri-valued AND + return Math.min(smode, spad); + } + + // separate methods for mode and padding + // called directly by Cipher only to throw the correct exception + int supportsMode(Service s) { + return supports(s, ATTR_MODE, mode); + } + int supportsPadding(Service s) { + return supports(s, ATTR_PAD, pad); + } + + private static int supports(Service s, String attrName, String value) { + if (value == null) { + return S_YES; + } + String regexp = s.getAttribute(attrName); + if (regexp == null) { + return S_MAYBE; + } + return matches(regexp, value) ? S_YES : S_NO; + } + + // ConcurrentMap<String,Pattern> for previously compiled patterns + private final static ConcurrentMap<String, Pattern> patternCache = + new ConcurrentHashMap<String, Pattern>(); + + private static boolean matches(String regexp, String str) { + Pattern pattern = patternCache.get(regexp); + if (pattern == null) { + pattern = Pattern.compile(regexp); + patternCache.putIfAbsent(regexp, pattern); + } + return pattern.matcher(str.toUpperCase(Locale.ENGLISH)).matches(); + } + + } + + private static List<Transform> getTransforms(String transformation) + throws NoSuchAlgorithmException { + String[] parts = tokenizeTransformation(transformation); + + String alg = parts[0]; + String mode = parts[1]; + String pad = parts[2]; + if ((mode != null) && (mode.length() == 0)) { + mode = null; + } + if ((pad != null) && (pad.length() == 0)) { + pad = null; + } + + if ((mode == null) && (pad == null)) { + // DES + Transform tr = new Transform(alg, "", null, null); + return Collections.singletonList(tr); + } else { // if ((mode != null) && (pad != null)) { + // DES/CBC/PKCS5Padding + List<Transform> list = new ArrayList<>(4); + list.add(new Transform(alg, "/" + mode + "/" + pad, null, null)); + list.add(new Transform(alg, "/" + mode, null, pad)); + list.add(new Transform(alg, "//" + pad, mode, null)); + list.add(new Transform(alg, "", mode, pad)); + return list; + } + } + + // get the transform matching the specified service + private static Transform getTransform(Service s, + List<Transform> transforms) { + String alg = s.getAlgorithm().toUpperCase(Locale.ENGLISH); + for (Transform tr : transforms) { + if (alg.endsWith(tr.suffix)) { + return tr; + } + } + return null; + } + */ + // END Android-removed: Reimplement provider selection. + /** * Returns a <code>Cipher</code> object that implements the specified * transformation. @@ -466,7 +644,7 @@ public class Cipher { throw new NoSuchProviderException("No such provider: " + provider); } - return createCipher(transformation, p); + return getInstance(transformation, p); } /** @@ -514,6 +692,7 @@ public class Cipher { static final Cipher createCipher(String transformation, Provider provider) throws NoSuchAlgorithmException, NoSuchPaddingException { + Providers.checkBouncyCastleDeprecation(provider, "Cipher", transformation); String[] tokenizedTransformation = tokenizeTransformation(transformation); CipherSpiAndProvider cipherSpiAndProvider = null; @@ -693,6 +872,112 @@ public class Cipher { return exmech; } + // BEGIN Android-removed: Eliminate crypto permission checking. + // Android doesn't implement SecurityManager permissions. + /* + // + // Crypto permission check code below + // + private void checkCryptoPerm(CipherSpi checkSpi, Key key) + throws InvalidKeyException { + if (cryptoPerm == CryptoAllPermission.INSTANCE) { + return; + } + // Check if key size and default parameters are within legal limits + AlgorithmParameterSpec params; + try { + params = getAlgorithmParameterSpec(checkSpi.engineGetParameters()); + } catch (InvalidParameterSpecException ipse) { + throw new InvalidKeyException + ("Unsupported default algorithm parameters"); + } + if (!passCryptoPermCheck(checkSpi, key, params)) { + throw new InvalidKeyException( + "Illegal key size or default parameters"); + } + } + + private void checkCryptoPerm(CipherSpi checkSpi, Key key, + AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (cryptoPerm == CryptoAllPermission.INSTANCE) { + return; + } + // Determine keysize and check if it is within legal limits + if (!passCryptoPermCheck(checkSpi, key, null)) { + throw new InvalidKeyException("Illegal key size"); + } + if ((params != null) && (!passCryptoPermCheck(checkSpi, key, params))) { + throw new InvalidAlgorithmParameterException("Illegal parameters"); + } + } + + private void checkCryptoPerm(CipherSpi checkSpi, Key key, + AlgorithmParameters params) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (cryptoPerm == CryptoAllPermission.INSTANCE) { + return; + } + // Convert the specified parameters into specs and then delegate. + AlgorithmParameterSpec pSpec; + try { + pSpec = getAlgorithmParameterSpec(params); + } catch (InvalidParameterSpecException ipse) { + throw new InvalidAlgorithmParameterException + ("Failed to retrieve algorithm parameter specification"); + } + checkCryptoPerm(checkSpi, key, pSpec); + } + + private boolean passCryptoPermCheck(CipherSpi checkSpi, Key key, + AlgorithmParameterSpec params) + throws InvalidKeyException { + String em = cryptoPerm.getExemptionMechanism(); + int keySize = checkSpi.engineGetKeySize(key); + // Use the "algorithm" component of the cipher + // transformation so that the perm check would + // work when the key has the "aliased" algo. + String algComponent; + int index = transformation.indexOf('/'); + if (index != -1) { + algComponent = transformation.substring(0, index); + } else { + algComponent = transformation; + } + CryptoPermission checkPerm = + new CryptoPermission(algComponent, keySize, params, em); + + if (!cryptoPerm.implies(checkPerm)) { + if (debug != null) { + debug.println("Crypto Permission check failed"); + debug.println("granted: " + cryptoPerm); + debug.println("requesting: " + checkPerm); + } + return false; + } + if (exmech == null) { + return true; + } + try { + if (!exmech.isCryptoAllowed(key)) { + if (debug != null) { + debug.println(exmech.getName() + " isn't enforced"); + } + return false; + } + } catch (ExemptionMechanismException eme) { + if (debug != null) { + debug.println("Cannot determine whether "+ + exmech.getName() + " has been enforced"); + eme.printStackTrace(); + } + return false; + } + return true; + } + */ + // END Android-removed: Eliminate crypto permission checking. + // check if opmode is one of the defined constants // throw InvalidParameterExeption if not private static void checkOpmode(int opmode) { @@ -836,6 +1121,7 @@ public class Cipher { initialized = true; this.opmode = opmode; + // Android-removed: this debugging mechanism is not used in Android. /* if (!skipDebug && pdebug != null) { @@ -1263,7 +1549,8 @@ public class Cipher { */ public final void init(int opmode, Certificate certificate, SecureRandom random) - throws InvalidKeyException { + throws InvalidKeyException + { initialized = false; checkOpmode(opmode); @@ -1272,28 +1559,28 @@ public class Cipher { if (certificate instanceof java.security.cert.X509Certificate) { // Check whether the cert has a key usage extension // marked as a critical extension. - X509Certificate cert = (X509Certificate) certificate; + X509Certificate cert = (X509Certificate)certificate; Set<String> critSet = cert.getCriticalExtensionOIDs(); if (critSet != null && !critSet.isEmpty() - && critSet.contains(KEY_USAGE_EXTENSION_OID)) { + && critSet.contains(KEY_USAGE_EXTENSION_OID)) { boolean[] keyUsageInfo = cert.getKeyUsage(); // keyUsageInfo[2] is for keyEncipherment; // keyUsageInfo[3] is for dataEncipherment. if ((keyUsageInfo != null) && - (((opmode == Cipher.ENCRYPT_MODE) && - (keyUsageInfo.length > 3) && - (keyUsageInfo[3] == false)) || - ((opmode == Cipher.WRAP_MODE) && - (keyUsageInfo.length > 2) && - (keyUsageInfo[2] == false)))) { + (((opmode == Cipher.ENCRYPT_MODE) && + (keyUsageInfo.length > 3) && + (keyUsageInfo[3] == false)) || + ((opmode == Cipher.WRAP_MODE) && + (keyUsageInfo.length > 2) && + (keyUsageInfo[2] == false)))) { throw new InvalidKeyException("Wrong key usage"); } } } PublicKey publicKey = - (certificate == null ? null : certificate.getPublicKey()); + (certificate==null? null:certificate.getPublicKey()); try { chooseProvider(InitType.KEY, opmode, (Key) publicKey, null, null, random); @@ -2346,6 +2633,8 @@ public class Cipher { spi.engineUpdateAAD(src); } + // BEGIN Android-added: Bulk of the new provider implementation. + // See note at top of class. /** * Returns the {@code CipherSpi} backing this {@code Cipher} or {@code null} if no * {@code CipherSpi} is backing this {@code Cipher}. @@ -2677,4 +2966,5 @@ public class Cipher { } return null; } + // END Android-added: Bulk of the new provider implementation. } |