diff options
178 files changed, 4380 insertions, 1414 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 3b5c2231ccf2..74e65d9a3ee9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -3255,6 +3255,7 @@ package android.animation { method public java.lang.Object getAnimatedValue(java.lang.String); method public long getCurrentPlayTime(); method public long getDuration(); + method public static float getDurationScale(); method public static long getFrameDelay(); method public int getRepeatCount(); method public int getRepeatMode(); @@ -3272,6 +3273,7 @@ package android.animation { method public void setCurrentFraction(float); method public void setCurrentPlayTime(long); method public android.animation.ValueAnimator setDuration(long); + method public static void setDurationScale(float); method public void setEvaluator(android.animation.TypeEvaluator); method public void setFloatValues(float...); method public static void setFrameDelay(long); diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 7465ed92e469..ed08a70bfd8d 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -18,6 +18,7 @@ package android.accounts; import static android.Manifest.permission.GET_ACCOUNTS; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.Size; @@ -28,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.res.Resources; import android.database.SQLException; import android.os.Build; @@ -265,6 +267,15 @@ public class AccountManager { "android.accounts.AccountAuthenticator"; public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator"; + /** + * Token for the special case where a UID has access only to an account + * but no authenticator specific auth tokens. + * + * @hide + */ + public static final String ACCOUNT_ACCESS_TOKEN = + "com.android.abbfd278-af8b-415d-af8b-7571d5dab133"; + private final Context mContext; private final IAccountManager mService; private final Handler mMainHandler; @@ -2960,4 +2971,49 @@ public class AccountManager { } }.start(); } + + /** + * Gets whether a given package under a user has access to an account. + * Can be called only from the system UID. + * + * @param account The account for which to check. + * @param packageName The package for which to check. + * @param userHandle The user for which to check. + * @return True if the package can access the account. + * + * @hide + */ + public boolean hasAccountAccess(@NonNull Account account, @NonNull String packageName, + @NonNull UserHandle userHandle) { + try { + return mService.hasAccountAccess(account, packageName, userHandle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Creates an intent to request access to a given account for a UID. + * The returned intent should be stated for a result where {@link + * Activity#RESULT_OK} result means access was granted whereas {@link + * Activity#RESULT_CANCELED} result means access wasn't granted. Can + * be called only from the system UID. + * + * @param account The account for which to request. + * @param packageName The package name which to request. + * @param userHandle The user for which to request. + * @return The intent to request account access or null if the package + * doesn't exist. + * + * @hide + */ + public IntentSender createRequestAccountAccessIntentSenderAsUser(@NonNull Account account, + @NonNull String packageName, @NonNull UserHandle userHandle) { + try { + return mService.createRequestAccountAccessIntentSenderAsUser(account, packageName, + userHandle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/accounts/AccountManagerInternal.java b/core/java/android/accounts/AccountManagerInternal.java new file mode 100644 index 000000000000..d777643950e6 --- /dev/null +++ b/core/java/android/accounts/AccountManagerInternal.java @@ -0,0 +1,44 @@ +/* + * 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 android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.RemoteCallback; + +/** + * Account manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class AccountManagerInternal { + + /** + * Requests that a given package is given access to an account. + * The provided callback will be invoked with a {@link android.os.Bundle} + * containing the result which will be a boolean value mapped to the + * {@link AccountManager#KEY_BOOLEAN_RESULT} key. + * + * @param account The account for which to request. + * @param packageName The package name for which to request. + * @param userId Concrete user id for which to request. + * @param callback A callback for receiving the result. + */ + public abstract void requestAccountAccess(@NonNull Account account, + @NonNull String packageName, @IntRange(from = 0) int userId, + @NonNull RemoteCallback callback); +} diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java index 12b2b9ccf148..8d0ce58d3358 100644 --- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java +++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java @@ -35,12 +35,10 @@ import java.io.IOException; */ public class GrantCredentialsPermissionActivity extends Activity implements View.OnClickListener { public static final String EXTRAS_ACCOUNT = "account"; - public static final String EXTRAS_AUTH_TOKEN_LABEL = "authTokenLabel"; public static final String EXTRAS_AUTH_TOKEN_TYPE = "authTokenType"; public static final String EXTRAS_RESPONSE = "response"; - public static final String EXTRAS_ACCOUNT_TYPE_LABEL = "accountTypeLabel"; - public static final String EXTRAS_PACKAGES = "application"; public static final String EXTRAS_REQUESTING_UID = "uid"; + private Account mAccount; private String mAuthTokenType; private int mUid; @@ -109,7 +107,11 @@ public class GrantCredentialsPermissionActivity extends Activity implements View } } }; - AccountManager.get(this).getAuthTokenLabel(mAccount.type, mAuthTokenType, callback, null); + + if (!AccountManager.ACCOUNT_ACCESS_TOKEN.equals(mAuthTokenType)) { + AccountManager.get(this).getAuthTokenLabel(mAccount.type, + mAuthTokenType, callback, null); + } findViewById(R.id.allow_button).setOnClickListener(this); findViewById(R.id.deny_button).setOnClickListener(this); diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 7199288426f2..56a6488088b5 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -19,8 +19,10 @@ package android.accounts; import android.accounts.IAccountManagerResponse; import android.accounts.Account; import android.accounts.AuthenticatorDescription; +import android.content.IntentSender; import android.os.Bundle; - +import android.os.RemoteCallback; +import android.os.UserHandle; /** * Central application service that provides account management. @@ -102,4 +104,10 @@ interface IAccountManager { /* Check if credentials update is suggested */ void isCredentialsUpdateSuggested(in IAccountManagerResponse response, in Account account, String statusToken); + + /* Check if the package in a user can access an account */ + boolean hasAccountAccess(in Account account, String packageName, in UserHandle userHandle); + /* Crate an intent to request account access for package and a given user id */ + IntentSender createRequestAccountAccessIntentSenderAsUser(in Account account, + String packageName, in UserHandle userHandle); } diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index e3f8fa49f9dd..7e16e3ece549 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -18,6 +18,7 @@ package android.animation; import android.annotation.CallSuper; import android.annotation.IntDef; +import android.annotation.TestApi; import android.os.Looper; import android.os.Trace; import android.util.AndroidRuntimeException; @@ -261,6 +262,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio /** * @hide */ + @TestApi public static void setDurationScale(float durationScale) { sDurationScale = durationScale; } @@ -268,6 +270,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio /** * @hide */ + @TestApi public static float getDurationScale() { return sDurationScale; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 2a12ac8f0567..b8c133e869f2 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2219,6 +2219,7 @@ public class DevicePolicyManager { * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} * @throws IllegalStateException if the calling user is locked or has a managed profile. + * @throws IllegalArgumentException if the password does not meet system requirements. */ public boolean resetPassword(String password, int flags) { throwIfParentInstance("resetPassword"); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3f18ea91c99a..f908abcc7ebd 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2761,8 +2761,10 @@ public abstract class Context { * <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for * handling management of network connections. * <dt> {@link #WIFI_SERVICE} ("wifi") - * <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of - * Wi-Fi connectivity. + * <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi + * connectivity. On releases before NYC, it should only be obtained from an application + * context, and not from any other derived context to avoid memory leaks within the calling + * process. * <dt> {@link #WIFI_P2P_SERVICE} ("wifip2p") * <dd> A {@link android.net.wifi.p2p.WifiP2pManager WifiP2pManager} for management of * Wi-Fi Direct connectivity. diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 4d9db98587ce..1cf23ae8d040 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4314,6 +4314,14 @@ public class Intent implements Parcelable, Cloneable { public static final int FLAG_DEBUG_TRIAGED_MISSING = 0x00000100; /** + * Internal flag used to indicate ephemeral applications should not be + * considered when resolving the intent. + * + * @hide + */ + public static final int FLAG_IGNORE_EPHEMERAL = 0x00000200; + + /** * If set, the new activity is not kept in the history stack. As soon as * the user navigates away from it, the activity is finished. This may also * be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory diff --git a/core/java/android/content/SyncAdapterType.java b/core/java/android/content/SyncAdapterType.java index 8a16ac94522e..6ef7fd214069 100644 --- a/core/java/android/content/SyncAdapterType.java +++ b/core/java/android/content/SyncAdapterType.java @@ -16,6 +16,7 @@ package android.content; +import android.annotation.Nullable; import android.text.TextUtils; import android.os.Parcelable; import android.os.Parcel; @@ -33,6 +34,7 @@ public class SyncAdapterType implements Parcelable { private final boolean isAlwaysSyncable; private final boolean allowParallelSyncs; private final String settingsActivity; + private final String packageName; public SyncAdapterType(String authority, String accountType, boolean userVisible, boolean supportsUploading) { @@ -50,6 +52,7 @@ public class SyncAdapterType implements Parcelable { this.allowParallelSyncs = false; this.settingsActivity = null; this.isKey = false; + this.packageName = null; } /** @hide */ @@ -57,7 +60,8 @@ public class SyncAdapterType implements Parcelable { boolean supportsUploading, boolean isAlwaysSyncable, boolean allowParallelSyncs, - String settingsActivity) { + String settingsActivity, + String packageName) { if (TextUtils.isEmpty(authority)) { throw new IllegalArgumentException("the authority must not be empty: " + authority); } @@ -72,6 +76,7 @@ public class SyncAdapterType implements Parcelable { this.allowParallelSyncs = allowParallelSyncs; this.settingsActivity = settingsActivity; this.isKey = false; + this.packageName = packageName; } private SyncAdapterType(String authority, String accountType) { @@ -89,6 +94,7 @@ public class SyncAdapterType implements Parcelable { this.allowParallelSyncs = false; this.settingsActivity = null; this.isKey = true; + this.packageName = null; } public boolean supportsUploading() { @@ -148,6 +154,16 @@ public class SyncAdapterType implements Parcelable { return settingsActivity; } + /** + * The package hosting the sync adapter. + * @return The package name. + * + * @hide + */ + public @Nullable String getPackageName() { + return packageName; + } + public static SyncAdapterType newKey(String authority, String accountType) { return new SyncAdapterType(authority, accountType); } @@ -181,6 +197,7 @@ public class SyncAdapterType implements Parcelable { + ", isAlwaysSyncable=" + isAlwaysSyncable + ", allowParallelSyncs=" + allowParallelSyncs + ", settingsActivity=" + settingsActivity + + ", packageName=" + packageName + "}"; } } @@ -201,6 +218,7 @@ public class SyncAdapterType implements Parcelable { dest.writeInt(isAlwaysSyncable ? 1 : 0); dest.writeInt(allowParallelSyncs ? 1 : 0); dest.writeString(settingsActivity); + dest.writeString(packageName); } public SyncAdapterType(Parcel source) { @@ -211,6 +229,7 @@ public class SyncAdapterType implements Parcelable { source.readInt() != 0, source.readInt() != 0, source.readInt() != 0, + source.readString(), source.readString()); } diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java index 6704b75dff7f..ddbdb8a7a559 100644 --- a/core/java/android/content/SyncAdaptersCache.java +++ b/core/java/android/content/SyncAdaptersCache.java @@ -81,7 +81,7 @@ public class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> sa.getString(com.android.internal.R.styleable .SyncAdapter_settingsActivity); return new SyncAdapterType(authority, accountType, userVisible, supportsUploading, - isAlwaysSyncable, allowParallelSyncs, settingsActivity); + isAlwaysSyncable, allowParallelSyncs, settingsActivity, packageName); } finally { sa.recycle(); } diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index c9be6edab424..b5df4d75a238 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -68,12 +68,6 @@ public class ResolveInfo implements Parcelable { public EphemeralResolveInfo ephemeralResolveInfo; /** - * A ResolveInfo that points at the ephemeral installer. - * @hide - */ - public ResolveInfo ephemeralInstaller; - - /** * The IntentFilter that was matched for this ResolveInfo. */ public IntentFilter filter; diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 0c3d4b3d7be6..d4dcacceb761 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -123,10 +123,18 @@ public final class ContextHubManager { /** * Load a nano app on a specified context hub. * + * Note that loading is asynchronous. When we return from this method, + * the nano app (probably) hasn't loaded yet. Assuming a return of 0 + * from this method, then the final success/failure for the load, along + * with the "handle" for the nanoapp, is all delivered in a byte + * string via a call to Callback.onMessageReceipt. + * + * TODO(b/30784270): Provide a better success/failure and "handle" delivery. + * * @param hubHandle handle of context hub to load the app on. * @param app the nanoApp to load on the hub * - * @return int nanoAppInstance of the loaded nanoApp on success, + * @return 0 if the command for loading was sent to the context hub; * -1 otherwise * * @see NanoApp @@ -150,9 +158,17 @@ public final class ContextHubManager { /** * Unload a specified nanoApp * - * @param nanoAppHandle handle of the nanoApp to load + * Note that unloading is asynchronous. When we return from this method, + * the nano app (probably) hasn't unloaded yet. Assuming a return of 0 + * from this method, then the final success/failure for the unload is + * delivered in a byte string via a call to Callback.onMessageReceipt. + * + * TODO(b/30784270): Provide a better success/failure delivery. * - * @return int 0 on success, -1 otherwise + * @param nanoAppHandle handle of the nanoApp to unload + * + * @return 0 if the command for unloading was sent to the context hub; + * -1 otherwise */ public int unloadNanoApp(int nanoAppHandle) { int retVal = -1; @@ -169,6 +185,24 @@ public final class ContextHubManager { /** * get information about the nano app instance * + * NOTE: The returned NanoAppInstanceInfo does _not_ contain correct + * information for several fields, specifically: + * - getName() + * - getPublisher() + * - getNeededExecMemBytes() + * - getNeededReadMemBytes() + * - getNeededWriteMemBytes() + * + * For example, say you call loadNanoApp() with a NanoApp that has + * getName() returning "My Name". Later, if you call getNanoAppInstanceInfo + * for that nanoapp, the returned NanoAppInstanceInfo's getName() + * method will claim "Preloaded app, unknown", even though you would + * have expected "My Name". For now, as the user, you'll need to + * separately track the above fields if they are of interest to you. + * + * TODO(b/30943489): Have the returned NanoAppInstanceInfo contain the + * correct information. + * * @param nanoAppHandle handle of the nanoAppInstance * @return NanoAppInstanceInfo Information about the nano app instance. * @@ -209,6 +243,14 @@ public final class ContextHubManager { /** * Send a message to a specific nano app instance on a context hub. * + * Note that the return value of this method only speaks of success + * up to the point of sending this to the Context Hub. It is not + * an assurance that the Context Hub successfully sent this message + * on to the nanoapp. If assurance is desired, a protocol should be + * established between your code and the nanoapp, with the nanoapp + * sending a confirmation message (which will be reported via + * Callback.onMessageReceipt). + * * @param hubHandle handle of the hub to send the message to * @param nanoAppHandle handle of the nano app to send to * @param message Message to be sent diff --git a/core/java/android/hardware/location/ContextHubService.java b/core/java/android/hardware/location/ContextHubService.java index 062c9580c1e4..eea2387d7dea 100644 --- a/core/java/android/hardware/location/ContextHubService.java +++ b/core/java/android/hardware/location/ContextHubService.java @@ -162,6 +162,28 @@ public class ContextHubService extends IContextHubService.Stub { msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_LOAD_NANO_APP; long appId = app.getAppId(); + // TODO(b/30808791): Remove this hack when the NanoApp API is fixed. + // Due to a bug in the NanoApp API, only the least significant four + // bytes of the app ID can be stored. The most significant five + // bytes of a normal app ID are the "vendor", and thus the most + // significant of the bytes we have is the least significant byte + // of the vendor. In the case that byte is the ASCII value for + // lower-case 'L', we assume the vendor is supposed to be "Googl" + // and fill in the four most significant bytes accordingly. + if ((appId >> 32) != 0) { + // We're unlikely to notice this warning, but at least + // we can avoid running our hack logic. + Log.w(TAG, "Code has not been updated since API fix."); + } else { + // Note: Lower-case 'L', not the number 1. + if (((appId >> 24) & 0xFF) == (long)'l') { + // Assume we're a Google nanoapp. + appId |= ((long)'G') << 56; + appId |= ((long)'o') << 48; + appId |= ((long)'o') << 40; + appId |= ((long)'g') << 32; + } + } msgHeader[HEADER_FIELD_LOAD_APP_ID_LO] = (int)(appId & 0xFFFFFFFF); msgHeader[HEADER_FIELD_LOAD_APP_ID_HI] = (int)((appId >> 32) & 0xFFFFFFFF); @@ -322,9 +344,16 @@ public class ContextHubService extends IContextHubService.Stub { appInfo.setNeededReadMemBytes(PRE_LOADED_APP_MEM_REQ); appInfo.setNeededWriteMemBytes(PRE_LOADED_APP_MEM_REQ); + String action; + if (mNanoAppHash.containsKey(appInstanceHandle)) { + action = "Updated"; + } else { + action = "Added"; + } + mNanoAppHash.put(appInstanceHandle, appInfo); - Log.d(TAG, "Added app instance " + appInstanceHandle + " with id " + appId - + " version " + appVersion); + Log.d(TAG, action + " app instance " + appInstanceHandle + " with id " + + appId + " version " + appVersion); return 0; } diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java index 8db70e9c53f1..bf35a3d6fbd6 100644 --- a/core/java/android/hardware/location/NanoAppFilter.java +++ b/core/java/android/hardware/location/NanoAppFilter.java @@ -43,7 +43,8 @@ public class NanoAppFilter { private long mAppIdVendorMask; // Id of the context hub this instance is expected on - private int mContextHubId; + // TODO: Provide an API which will let us change this HubId. + private int mContextHubId = HUB_ANY; /** * Flag indicating any version. With this flag set, all versions shall match provided version. diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java index 71a5a8816f58..ac6d83f622b2 100644 --- a/core/java/android/hardware/location/NanoAppInstanceInfo.java +++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java @@ -113,7 +113,12 @@ public class NanoAppInstanceInfo { } /** - * Set the application version + * Get the application version + * + * NOTE: There is a race condition where shortly after loading, this + * may return -1 instead of the correct version. + * + * TODO(b/30970527): Fix this race condition. * * @return int - version of the app */ diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index 9cd563e87c4a..d570e66a2435 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -175,7 +175,11 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { if (isNetworkTypeMobile(type)) { if (state.subscriberId == null) { - Slog.w(TAG, "Active mobile network without subscriber!"); + if (state.networkInfo.getState() != NetworkInfo.State.DISCONNECTED && + state.networkInfo.getState() != NetworkInfo.State.UNKNOWN) { + Slog.w(TAG, "Active mobile network without subscriber! ni = " + + state.networkInfo); + } } subscriberId = state.subscriberId; diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java index 4f4e7223acb9..9a4b599f0e9b 100644 --- a/core/java/android/os/AsyncTask.java +++ b/core/java/android/os/AsyncTask.java @@ -298,12 +298,16 @@ public abstract class AsyncTask<Params, Progress, Result> { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); - - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - //noinspection unchecked - Result result = doInBackground(mParams); - Binder.flushPendingCommands(); - return postResult(result); + Result result = null; + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + //noinspection unchecked + result = doInBackground(mParams); + Binder.flushPendingCommands(); + } finally { + postResult(result); + } + return result; } }; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 300deea1c873..0b428d43ed16 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5839,6 +5839,8 @@ public final class Settings { /** * If nonzero, ANRs in invisible background processes bring up a dialog. * Otherwise, the process will be silently killed. + * + * Also prevents ANRs and crash dialogs from being suppressed. * @hide */ public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; @@ -6324,6 +6326,13 @@ public final class Settings { = "demo_user_setup_complete"; /** + * Specifies whether the web action API is enabled. + * + * @hide + */ + public static final String WEB_ACTION_ENABLED = "web_action_enabled"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -8729,6 +8738,16 @@ public final class Settings { public static final String RETAIL_DEMO_MODE_CONSTANTS = "retail_demo_mode_constants"; /** + * The reason for the settings database being downgraded. This is only for + * troubleshooting purposes and its value should not be interpreted in any way. + * + * Type: string + * + * @hide + */ + public static final String DATABASE_DOWNGRADE_REASON = "database_downgrade_reason"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 286e0979d12b..f92d83af93a0 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -96,6 +96,8 @@ public class Surface implements Parcelable { private HwuiContext mHwuiContext; + private boolean mIsSingleBuffered; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({SCALING_MODE_FREEZE, SCALING_MODE_SCALE_TO_WINDOW, @@ -158,7 +160,7 @@ public class Surface implements Parcelable { if (surfaceTexture == null) { throw new IllegalArgumentException("surfaceTexture must not be null"); } - + mIsSingleBuffered = surfaceTexture.isSingleBuffered(); synchronized (mLock) { mName = surfaceTexture.toString(); setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture)); @@ -457,7 +459,10 @@ public class Surface implements Parcelable { // create a new native Surface and return it after reducing // the reference count on mNativeObject. Either way, it is // not necessary to call nativeRelease() here. + // NOTE: This must be kept synchronized with the native parceling code + // in frameworks/native/libs/Surface.cpp mName = source.readString(); + mIsSingleBuffered = source.readInt() != 0; setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source)); } } @@ -468,7 +473,10 @@ public class Surface implements Parcelable { throw new IllegalArgumentException("dest must not be null"); } synchronized (mLock) { + // NOTE: This must be kept synchronized with the native parceling code + // in frameworks/native/libs/Surface.cpp dest.writeString(mName); + dest.writeInt(mIsSingleBuffered ? 1 : 0); nativeWriteToParcel(mNativeObject, dest); } if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { @@ -531,6 +539,14 @@ public class Surface implements Parcelable { } /** + * Returns whether or not this Surface is backed by a single-buffered SurfaceTexture + * @hide + */ + public boolean isSingleBuffered() { + return mIsSingleBuffered; + } + + /** * Exception thrown when a Canvas couldn't be locked with {@link Surface#lockCanvas}, or * when a SurfaceTexture could not successfully be allocated. */ diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 48189106f2c4..e0ac7ed79a15 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -193,26 +193,20 @@ public class SurfaceView extends View { private boolean mGlobalListenersAdded; public SurfaceView(Context context) { - super(context); - init(); + this(context, null); } public SurfaceView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + this(context, attrs, 0); } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); + this(context, attrs, defStyleAttr, 0); } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - init(); - } - private void init() { setWillNotDraw(true); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c42ad2ea2e61..908658feb29c 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -20280,8 +20280,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // remove it from the transparent region. final int[] location = attachInfo.mTransparentLocation; getLocationInWindow(location); - region.op(location[0], location[1], location[0] + mRight - mLeft, - location[1] + mBottom - mTop, Region.Op.DIFFERENCE); + // When a view has Z value, then it will be better to leave some area below the view + // for drawing shadow. The shadow outset is proportional to the Z value. Note that + // the bottom part needs more offset than the left, top and right parts due to the + // spot light effects. + int shadowOffset = getZ() > 0 ? (int) getZ() : 0; + region.op(location[0] - shadowOffset, location[1] - shadowOffset, + location[0] + mRight - mLeft + shadowOffset, + location[1] + mBottom - mTop + (shadowOffset * 3), Region.Op.DIFFERENCE); } else { if (mBackground != null && mBackground.getOpacity() != PixelFormat.TRANSPARENT) { // The SKIP_DRAW flag IS set and the background drawable exists, we remove diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 3ff8d4f2e2b1..6933efc15b68 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -6406,16 +6406,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return true; } super.gatherTransparentRegion(region); - final View[] children = mChildren; - final int count = mChildrenCount; + // Instead of naively traversing the view tree, we have to traverse according to the Z + // order here. We need to go with the same order as dispatchDraw(). + // One example is that after surfaceView punch a hole, we will still allow other views drawn + // on top of that hole. In this case, those other views should be able to cut the + // transparent region into smaller area. + final int childrenCount = mChildrenCount; boolean noneOfTheChildrenAreTransparent = true; - for (int i = 0; i < count; i++) { - final View child = children[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { - if (!child.gatherTransparentRegion(region)) { - noneOfTheChildrenAreTransparent = false; + if (childrenCount > 0) { + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + final View[] children = mChildren; + for (int i = 0; i < childrenCount; i++) { + final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); + final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { + if (!child.gatherTransparentRegion(region)) { + noneOfTheChildrenAreTransparent = false; + } } } + if (preorderedList != null) preorderedList.clear(); } return meOpaque || noneOfTheChildrenAreTransparent; } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index b52e4b019f8f..2b3d6436dd4e 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -507,6 +507,11 @@ public interface WindowManagerPolicy { * Retrieves the {@param outBounds} from the stack with id {@param stackId}. */ void getStackBounds(int stackId, Rect outBounds); + + /** + * Overrides all currently playing app animations with {@param a}. + */ + void overridePlayingAppAnimationsLw(Animation a); } public interface PointerEventListener { diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 6432f703f2ba..5935c7889e9c 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1547,7 +1547,7 @@ public class PopupWindow { } // Let the window manager know to align the top to y. - outParams.gravity = Gravity.LEFT | Gravity.TOP; + outParams.gravity = computeGravity(); outParams.width = width; outParams.height = height; diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index f2fc617ccc33..10959780683a 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -16,19 +16,19 @@ package android.widget; +import com.android.internal.R; + +import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.Widget; import android.content.Context; -import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; -import android.os.Parcelable.Creator; import android.util.AttributeSet; +import android.util.MathUtils; import android.view.View; import android.view.accessibility.AccessibilityEvent; -import com.android.internal.R; import java.util.Locale; @@ -102,8 +102,8 @@ public class TimePicker extends FrameLayout { * @param hour the hour to set, in the range (0-23) * @see #getHour() */ - public void setHour(int hour) { - mDelegate.setHour(hour); + public void setHour(@IntRange(from = 0, to = 23) int hour) { + mDelegate.setHour(MathUtils.constrain(hour, 0, 23)); } /** @@ -117,13 +117,13 @@ public class TimePicker extends FrameLayout { } /** - * Sets the currently selected minute.. + * Sets the currently selected minute. * * @param minute the minute to set, in the range (0-59) * @see #getMinute() */ - public void setMinute(int minute) { - mDelegate.setMinute(minute); + public void setMinute(@IntRange(from = 0, to = 59) int minute) { + mDelegate.setMinute(MathUtils.constrain(minute, 0, 59)); } /** @@ -137,8 +137,9 @@ public class TimePicker extends FrameLayout { } /** - * Sets the current hour. + * Sets the currently selected hour using 24-hour time. * + * @param currentHour the hour to set, in the range (0-23) * @deprecated Use {@link #setHour(int)} */ @Deprecated @@ -147,33 +148,34 @@ public class TimePicker extends FrameLayout { } /** - * @return the current hour in the range (0-23) + * @return the currently selected hour, in the range (0-23) * @deprecated Use {@link #getHour()} */ @NonNull @Deprecated public Integer getCurrentHour() { - return mDelegate.getHour(); + return getHour(); } /** - * Set the current minute (0-59). + * Sets the currently selected minute. * + * @param currentMinute the minute to set, in the range (0-59) * @deprecated Use {@link #setMinute(int)} */ @Deprecated public void setCurrentMinute(@NonNull Integer currentMinute) { - mDelegate.setMinute(currentMinute); + setMinute(currentMinute); } /** - * @return the current minute + * @return the currently selected minute, in the range (0-59) * @deprecated Use {@link #getMinute()} */ @NonNull @Deprecated public Integer getCurrentMinute() { - return mDelegate.getMinute(); + return getMinute(); } /** @@ -256,10 +258,10 @@ public class TimePicker extends FrameLayout { * for the real behavior. */ interface TimePickerDelegate { - void setHour(int hour); + void setHour(@IntRange(from = 0, to = 23) int hour); int getHour(); - void setMinute(int minute); + void setMinute(@IntRange(from = 0, to = 59) int minute); int getMinute(); void setIs24Hour(boolean is24Hour); diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index f084db2d1589..b973324dee8c 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -70,46 +70,49 @@ import java.util.Vector; */ public class VideoView extends SurfaceView implements MediaPlayerControl, SubtitleController.Anchor { - private String TAG = "VideoView"; - // settable by the client - private Uri mUri; - private Map<String, String> mHeaders; + private static final String TAG = "VideoView"; // all possible internal states - private static final int STATE_ERROR = -1; - private static final int STATE_IDLE = 0; - private static final int STATE_PREPARING = 1; - private static final int STATE_PREPARED = 2; - private static final int STATE_PLAYING = 3; - private static final int STATE_PAUSED = 4; + private static final int STATE_ERROR = -1; + private static final int STATE_IDLE = 0; + private static final int STATE_PREPARING = 1; + private static final int STATE_PREPARED = 2; + private static final int STATE_PLAYING = 3; + private static final int STATE_PAUSED = 4; private static final int STATE_PLAYBACK_COMPLETED = 5; + private final Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks = new Vector<>(); + + // settable by the client + private Uri mUri; + private Map<String, String> mHeaders; + // mCurrentState is a VideoView object's current state. // mTargetState is the state that a method caller intends to reach. // For instance, regardless the VideoView object's current state, // calling pause() intends to bring the object to a target state // of STATE_PAUSED. private int mCurrentState = STATE_IDLE; - private int mTargetState = STATE_IDLE; + private int mTargetState = STATE_IDLE; // All the stuff we need for playing and showing a video private SurfaceHolder mSurfaceHolder = null; private MediaPlayer mMediaPlayer = null; - private int mAudioSession; - private int mVideoWidth; - private int mVideoHeight; - private int mSurfaceWidth; - private int mSurfaceHeight; + private int mAudioSession; + private int mVideoWidth; + private int mVideoHeight; + private int mSurfaceWidth; + private int mSurfaceHeight; private MediaController mMediaController; private OnCompletionListener mOnCompletionListener; private MediaPlayer.OnPreparedListener mOnPreparedListener; - private int mCurrentBufferPercentage; + private int mCurrentBufferPercentage; private OnErrorListener mOnErrorListener; - private OnInfoListener mOnInfoListener; - private int mSeekWhenPrepared; // recording the seek position while preparing - private boolean mCanPause; - private boolean mCanSeekBack; - private boolean mCanSeekForward; + private OnInfoListener mOnInfoListener; + private int mSeekWhenPrepared; // recording the seek position while preparing + private boolean mCanPause; + private boolean mCanSeekBack; + private boolean mCanSeekForward; /** Subtitle rendering widget overlaid on top of the video. */ private RenderingWidget mSubtitleWidget; @@ -118,13 +121,11 @@ public class VideoView extends SurfaceView private RenderingWidget.OnChangedListener mSubtitlesChangedListener; public VideoView(Context context) { - super(context); - initVideoView(); + this(context, null); } public VideoView(Context context, AttributeSet attrs) { this(context, attrs, 0); - initVideoView(); } public VideoView(Context context, AttributeSet attrs, int defStyleAttr) { @@ -133,7 +134,19 @@ public class VideoView extends SurfaceView public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - initVideoView(); + + mVideoWidth = 0; + mVideoHeight = 0; + + getHolder().addCallback(mSHCallback); + getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; } @Override @@ -209,19 +222,6 @@ public class VideoView extends SurfaceView return getDefaultSize(desiredSize, measureSpec); } - private void initVideoView() { - mVideoWidth = 0; - mVideoHeight = 0; - getHolder().addCallback(mSHCallback); - getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - mPendingSubtitleTracks = new Vector<Pair<InputStream, MediaFormat>>(); - mCurrentState = STATE_IDLE; - mTargetState = STATE_IDLE; - } - /** * Sets video path. * @@ -294,8 +294,6 @@ public class VideoView extends SurfaceView } } - private Vector<Pair<InputStream, MediaFormat>> mPendingSubtitleTracks; - public void stopPlayback() { if (mMediaPlayer != null) { mMediaPlayer.stop(); diff --git a/core/java/com/android/internal/graphics/drawable/AnimationScaleListDrawable.java b/core/java/com/android/internal/graphics/drawable/AnimationScaleListDrawable.java new file mode 100644 index 000000000000..62f18eac4aa4 --- /dev/null +++ b/core/java/com/android/internal/graphics/drawable/AnimationScaleListDrawable.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.graphics.drawable; + +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.DrawableContainer; +import android.util.AttributeSet; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * An internal DrawableContainer class, used to draw different things depending on animation scale. + * i.e: animation scale can be 0 in battery saver mode. + * This class contains 2 drawable, one is animatable, the other is static. When animation scale is + * not 0, the animatable drawable will the drawn. Otherwise, the static drawable will be drawn. + * <p>This class implements Animatable since ProgressBar can pick this up similarly as an + * AnimatedVectorDrawable. + * <p>It can be defined in an XML file with the {@code <AnimationScaleListDrawable>} + * element. + */ +public class AnimationScaleListDrawable extends DrawableContainer implements Animatable { + private static final String TAG = "AnimationScaleListDrawable"; + private AnimationScaleListState mAnimationScaleListState; + private boolean mMutated; + + public AnimationScaleListDrawable() { + this(null, null); + } + + private AnimationScaleListDrawable(@Nullable AnimationScaleListState state, + @Nullable Resources res) { + // Every scale list drawable has its own constant state. + final AnimationScaleListState newState = new AnimationScaleListState(state, this, res); + setConstantState(newState); + onStateChange(getState()); + } + + /** + * Set the current drawable according to the animation scale. If scale is 0, then pick the + * static drawable, otherwise, pick the animatable drawable. + */ + @Override + protected boolean onStateChange(int[] stateSet) { + final boolean changed = super.onStateChange(stateSet); + int idx = mAnimationScaleListState.getCurrentDrawableIndexBasedOnScale(); + return selectDrawable(idx) || changed; + } + + + @Override + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.AnimationScaleListDrawable); + updateDensity(r); + a.recycle(); + + inflateChildElements(r, parser, attrs, theme); + + onStateChange(getState()); + } + + /** + * Inflates child elements from XML. + */ + private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final AnimationScaleListState state = mAnimationScaleListState; + final int innerDepth = parser.getDepth() + 1; + int type; + int depth; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth + || type != XmlPullParser.END_TAG)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + + if (depth > innerDepth || !parser.getName().equals("item")) { + continue; + } + + // Either pick up the android:drawable attribute. + final TypedArray a = obtainAttributes(r, theme, attrs, + R.styleable.AnimationScaleListDrawableItem); + Drawable dr = a.getDrawable(R.styleable.AnimationScaleListDrawableItem_drawable); + a.recycle(); + + // Or parse the child element under <item>. + if (dr == null) { + while ((type = parser.next()) == XmlPullParser.TEXT) { + } + if (type != XmlPullParser.START_TAG) { + throw new XmlPullParserException( + parser.getPositionDescription() + + ": <item> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + dr = Drawable.createFromXmlInner(r, parser, attrs, theme); + } + + state.addDrawable(dr); + } + } + + @Override + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mAnimationScaleListState.mutate(); + mMutated = true; + } + return this; + } + + @Override + public void clearMutated() { + super.clearMutated(); + mMutated = false; + } + + @Override + public void start() { + Drawable dr = getCurrent(); + if (dr != null && dr instanceof Animatable) { + ((Animatable) dr).start(); + } + } + + @Override + public void stop() { + Drawable dr = getCurrent(); + if (dr != null && dr instanceof Animatable) { + ((Animatable) dr).stop(); + } + } + + @Override + public boolean isRunning() { + boolean result = false; + Drawable dr = getCurrent(); + if (dr != null && dr instanceof Animatable) { + result = ((Animatable) dr).isRunning(); + } + return result; + } + + static class AnimationScaleListState extends DrawableContainerState { + int[] mThemeAttrs = null; + // The index of the last static drawable. + int mStaticDrawableIndex = -1; + // The index of the last animatable drawable. + int mAnimatableDrawableIndex = -1; + + AnimationScaleListState(AnimationScaleListState orig, AnimationScaleListDrawable owner, + Resources res) { + super(orig, owner, res); + + if (orig != null) { + // Perform a shallow copy and rely on mutate() to deep-copy. + mThemeAttrs = orig.mThemeAttrs; + + mStaticDrawableIndex = orig.mStaticDrawableIndex; + mAnimatableDrawableIndex = orig.mAnimatableDrawableIndex; + } + + } + + void mutate() { + mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null; + } + + /** + * Add the drawable into the container. + * This class only keep track one animatable drawable, and one static. If there are multiple + * defined in the XML, then pick the last one. + */ + int addDrawable(Drawable drawable) { + final int pos = addChild(drawable); + if (drawable instanceof Animatable) { + mAnimatableDrawableIndex = pos; + } else { + mStaticDrawableIndex = pos; + } + return pos; + } + + @Override + public Drawable newDrawable() { + return new AnimationScaleListDrawable(this, null); + } + + @Override + public Drawable newDrawable(Resources res) { + return new AnimationScaleListDrawable(this, res); + } + + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null || super.canApplyTheme(); + } + + public int getCurrentDrawableIndexBasedOnScale() { + if (ValueAnimator.getDurationScale() == 0) { + return mStaticDrawableIndex; + } + return mAnimatableDrawableIndex; + } + } + + @Override + public void applyTheme(@NonNull Theme theme) { + super.applyTheme(theme); + + onStateChange(getState()); + } + + @Override + protected void setConstantState(@NonNull DrawableContainerState state) { + super.setConstantState(state); + + if (state instanceof AnimationScaleListState) { + mAnimationScaleListState = (AnimationScaleListState) state; + } + } +} + diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index 83d75fba80f9..e51ad3f737b8 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -28,8 +28,9 @@ oneway interface IKeyguardService { * FLAG_SHOW_ON_LOCK_SCREEN. * * @param isOccluded Whether the Keyguard is occluded by another window. + * @param animate Whether to play an animation for the state change. */ - void setOccluded(boolean isOccluded); + void setOccluded(boolean isOccluded, boolean animate); void addStateMonitorCallback(IKeyguardStateCallback callback); void verifyUnlock(IKeyguardExitCallback callback); diff --git a/core/java/com/android/internal/widget/AlertDialogLayout.java b/core/java/com/android/internal/widget/AlertDialogLayout.java index 891c920af336..9bf094891f3b 100644 --- a/core/java/com/android/internal/widget/AlertDialogLayout.java +++ b/core/java/com/android/internal/widget/AlertDialogLayout.java @@ -20,7 +20,9 @@ import android.annotation.AttrRes; import android.annotation.Nullable; import android.annotation.StyleRes; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; @@ -265,4 +267,92 @@ public class AlertDialogLayout extends LinearLayout { return 0; } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int paddingLeft = mPaddingLeft; + + // Where right end of child should go + final int width = right - left; + final int childRight = width - mPaddingRight; + + // Space available for child + final int childSpace = width - paddingLeft - mPaddingRight; + + final int totalLength = getMeasuredHeight(); + final int count = getChildCount(); + final int gravity = getGravity(); + final int majorGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; + final int minorGravity = gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; + + int childTop; + switch (majorGravity) { + case Gravity.BOTTOM: + // totalLength contains the padding already + childTop = mPaddingTop + bottom - top - totalLength; + break; + + // totalLength contains the padding already + case Gravity.CENTER_VERTICAL: + childTop = mPaddingTop + (bottom - top - totalLength) / 2; + break; + + case Gravity.TOP: + default: + childTop = mPaddingTop; + break; + } + + final Drawable dividerDrawable = getDividerDrawable(); + final int dividerHeight = dividerDrawable == null ? + 0 : dividerDrawable.getIntrinsicHeight(); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child != null && child.getVisibility() != GONE) { + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + + final LinearLayout.LayoutParams lp = + (LinearLayout.LayoutParams) child.getLayoutParams(); + + int layoutGravity = lp.gravity; + if (layoutGravity < 0) { + layoutGravity = minorGravity; + } + final int layoutDirection = getLayoutDirection(); + final int absoluteGravity = Gravity.getAbsoluteGravity( + layoutGravity, layoutDirection); + + final int childLeft; + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + childLeft = paddingLeft + ((childSpace - childWidth) / 2) + + lp.leftMargin - lp.rightMargin; + break; + + case Gravity.RIGHT: + childLeft = childRight - childWidth - lp.rightMargin; + break; + + case Gravity.LEFT: + default: + childLeft = paddingLeft + lp.leftMargin; + break; + } + + if (hasDividerBeforeChildAt(i)) { + childTop += dividerHeight; + } + + childTop += lp.topMargin; + setChildFrame(child, childLeft, childTop, childWidth, childHeight); + childTop += childHeight + lp.bottomMargin; + } + } + } + + private void setChildFrame(View child, int left, int top, int width, int height) { + child.layout(left, top, left + width, top + height); + } } diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index fbc51cdca6c2..5a50fbfd5277 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -79,6 +79,9 @@ public class BootReceiver extends BroadcastReceiver { private static final String LOG_FILES_FILE = "log-files.xml"; private static final AtomicFile sFile = new AtomicFile(new File( Environment.getDataSystemDirectory(), LOG_FILES_FILE)); + private static final String LAST_HEADER_FILE = "last-header.txt"; + private static final File lastHeaderFile = new File( + Environment.getDataSystemDirectory(), LAST_HEADER_FILE); @Override public void onReceive(final Context context, Intent intent) { @@ -113,9 +116,17 @@ public class BootReceiver extends BroadcastReceiver { Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); } - private void logBootEvents(Context ctx) throws IOException { - final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); - final String headers = new StringBuilder(512) + private String getPreviousBootHeaders() { + try { + return FileUtils.readTextFile(lastHeaderFile, 0, null); + } catch (IOException e) { + Slog.e(TAG, "Error reading " + lastHeaderFile, e); + return null; + } + } + + private String getCurrentBootHeaders() throws IOException { + return new StringBuilder(512) .append("Build: ").append(Build.FINGERPRINT).append("\n") .append("Hardware: ").append(Build.BOARD).append("\n") .append("Revision: ") @@ -125,6 +136,31 @@ public class BootReceiver extends BroadcastReceiver { .append("Kernel: ") .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) .append("\n").toString(); + } + + + private String getBootHeadersToLogAndUpdate() throws IOException { + final String oldHeaders = getPreviousBootHeaders(); + final String newHeaders = getCurrentBootHeaders(); + + try { + FileUtils.stringToFile(lastHeaderFile, newHeaders); + } catch (IOException e) { + Slog.e(TAG, "Error writing " + lastHeaderFile, e); + } + + if (oldHeaders == null) { + // If we failed to read the old headers, use the current headers + // but note this in the headers so we know + return "isPrevious: false\n" + newHeaders; + } + + return "isPrevious: true\n" + oldHeaders; + } + + private void logBootEvents(Context ctx) throws IOException { + final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE); + final String headers = getBootHeadersToLogAndUpdate(); final String bootReason = SystemProperties.get("ro.boot.bootreason", null); String recovery = RecoverySystem.handleAftermath(ctx); diff --git a/core/jni/android/graphics/pdf/PdfEditor.cpp b/core/jni/android/graphics/pdf/PdfEditor.cpp index d2d39cd0b286..0468b644862d 100644 --- a/core/jni/android/graphics/pdf/PdfEditor.cpp +++ b/core/jni/android/graphics/pdf/PdfEditor.cpp @@ -52,11 +52,9 @@ static struct { } gRectClassInfo; // Also used in PdfRenderer.cpp -Mutex sPdfiumLock; int sUnmatchedPdfiumInitRequestCount = 0; static void initializeLibraryIfNeeded() { - Mutex::Autolock _l(sPdfiumLock); if (sUnmatchedPdfiumInitRequestCount == 0) { FPDF_InitLibrary(); } @@ -64,7 +62,6 @@ static void initializeLibraryIfNeeded() { } static void destroyLibraryIfNeeded() { - Mutex::Autolock _l(sPdfiumLock); sUnmatchedPdfiumInitRequestCount--; if (sUnmatchedPdfiumInitRequestCount == 0) { FPDF_DestroyLibrary(); diff --git a/core/jni/android/graphics/pdf/PdfRenderer.cpp b/core/jni/android/graphics/pdf/PdfRenderer.cpp index 71bec7845fd7..43550ac9ed72 100644 --- a/core/jni/android/graphics/pdf/PdfRenderer.cpp +++ b/core/jni/android/graphics/pdf/PdfRenderer.cpp @@ -44,11 +44,9 @@ static struct { } gPointClassInfo; // See PdfEditor.cpp -extern Mutex sPdfiumLock; extern int sUnmatchedPdfiumInitRequestCount; static void initializeLibraryIfNeeded() { - Mutex::Autolock _l(sPdfiumLock); if (sUnmatchedPdfiumInitRequestCount == 0) { FPDF_InitLibrary(); } @@ -56,7 +54,6 @@ static void initializeLibraryIfNeeded() { } static void destroyLibraryIfNeeded() { - Mutex::Autolock _l(sPdfiumLock); sUnmatchedPdfiumInitRequestCount--; if (sUnmatchedPdfiumInitRequestCount == 0) { FPDF_DestroyLibrary(); diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp index bb09d001037d..e96613b7d45b 100644 --- a/core/jni/android_database_CursorWindow.cpp +++ b/core/jni/android_database_CursorWindow.cpp @@ -16,6 +16,7 @@ #undef LOG_TAG #define LOG_TAG "CursorWindow" +#define LOG_NDEBUG 0 #include <inttypes.h> #include <jni.h> @@ -30,6 +31,11 @@ #include <stdio.h> #include <string.h> #include <unistd.h> +#include <sys/types.h> +#include <dirent.h> + +#undef LOG_NDEBUG +#define LOG_NDEBUG 1 #include <androidfw/CursorWindow.h> #include "android_os_Parcel.h" @@ -61,6 +67,22 @@ static void throwUnknownTypeException(JNIEnv * env, jint type) { jniThrowException(env, "java/lang/IllegalStateException", msg.string()); } +static int getFdCount() { + char fdpath[PATH_MAX]; + int count = 0; + snprintf(fdpath, PATH_MAX, "/proc/%d/fd", getpid()); + DIR *dir = opendir(fdpath); + if (dir != NULL) { + struct dirent *dirent; + while ((dirent = readdir(dir))) { + count++; + } + count -= 2; // discount "." and ".." + closedir(dir); + } + return count; +} + static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) { String8 name; const char* nameStr = env->GetStringUTFChars(nameObj, NULL); @@ -85,7 +107,8 @@ static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj CursorWindow* window; status_t status = CursorWindow::createFromParcel(parcel, &window); if (status || !window) { - ALOGE("Could not create CursorWindow from Parcel due to error %d.", status); + ALOGE("Could not create CursorWindow from Parcel due to error %d, process fd count=%d", + status, getFdCount()); return 0; } diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp index 9515a0e336aa..36444102fb07 100644 --- a/core/jni/android_hardware_location_ContextHubService.cpp +++ b/core/jni/android_hardware_location_ContextHubService.cpp @@ -26,6 +26,10 @@ #include <stdint.h> #include <stdio.h> #include <stdlib.h> + +// TOOD: On master, alphabetize these and move <mutex> into this +// grouping. +#include <chrono> #include <unordered_map> #include <queue> @@ -34,11 +38,12 @@ #include "JNIHelp.h" #include "core_jni_helpers.h" -static constexpr int OS_APP_ID = -1; +static constexpr jint OS_APP_ID = -1; +static constexpr jint INVALID_APP_ID = -2; static constexpr uint64_t ALL_APPS = UINT64_C(0xFFFFFFFFFFFFFFFF); -static constexpr int MIN_APP_ID = 1; -static constexpr int MAX_APP_ID = 128; +static constexpr jint MIN_APP_ID = 1; +static constexpr jint MAX_APP_ID = 128; static constexpr size_t MSG_HEADER_SIZE = 4; static constexpr size_t HEADER_FIELD_MSG_TYPE = 0; @@ -50,6 +55,10 @@ static constexpr size_t HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE; static constexpr size_t HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1; static constexpr size_t MSG_HEADER_SIZE_LOAD_APP = MSG_HEADER_SIZE + 2; +// Monotonically increasing clock we use to determine if we can cancel +// a transaction. +using std::chrono::steady_clock; + namespace android { namespace { @@ -102,10 +111,21 @@ struct context_hub_info_s { struct app_instance_info_s { uint64_t truncName; // Possibly truncated name for logging uint32_t hubHandle; // Id of the hub this app is on - int instanceId; // system wide unique instance id - assigned + jint instanceId; // system wide unique instance id - assigned struct hub_app_info appInfo; // returned from the HAL }; + +// If a transaction takes longer than this, we'll allow it to be +// canceled by a new transaction. Note we do _not_ automatically +// cancel a transaction after this much time. We can have a +// legal transaction which takes longer than this amount of time, +// as long as no other new transactions are attempted after this +// time has expired. +// TODO(b/31105001): Establish a clean timing approach for all +// of our HAL interactions. +constexpr auto kMinTransactionCancelTime = std::chrono::seconds(29); + /* * TODO(ashutoshj): From original code review: * @@ -147,14 +167,15 @@ struct txnManager_s { std::mutex m; // mutex for manager hub_messages_e txnIdentifier; // What are we doing void *txnData; // Details + steady_clock::time_point firstTimeTxnCanBeCanceled; }; struct contextHubServiceDb_s { int initialized; context_hub_info_s hubInfo; jniInfo_s jniInfo; - std::queue<int> freeIds; - std::unordered_map<int, app_instance_info_s> appInstances; + std::queue<jint> freeIds; + std::unordered_map<jint, app_instance_info_s> appInstances; txnManager_s txnManager; }; @@ -176,25 +197,40 @@ static int addTxn(hub_messages_e txnIdentifier, void *txnData) { std::lock_guard<std::mutex>lock(mgr->m); mgr->txnPending = true; + mgr->firstTimeTxnCanBeCanceled = steady_clock::now() + + kMinTransactionCancelTime; mgr->txnData = txnData; mgr->txnIdentifier = txnIdentifier; return 0; } -static int closeTxn() { +// Only call this if you hold the db.txnManager.m lock. +static void closeTxnUnlocked() { txnManager_s *mgr = &db.txnManager; - std::lock_guard<std::mutex>lock(mgr->m); mgr->txnPending = false; free(mgr->txnData); mgr->txnData = nullptr; +} +static int closeTxn() { + std::lock_guard<std::mutex>lock(db.txnManager.m); + closeTxnUnlocked(); return 0; } +// If a transaction has been pending for longer than +// kMinTransactionCancelTime, this call will "cancel" that +// transaction and return that there are none pending. static bool isTxnPending() { txnManager_s *mgr = &db.txnManager; std::lock_guard<std::mutex>lock(mgr->m); + if (mgr->txnPending) { + if (steady_clock::now() >= mgr->firstTimeTxnCanBeCanceled) { + ALOGW("Transaction canceled"); + closeTxnUnlocked(); + } + } return mgr->txnPending; } @@ -259,16 +295,17 @@ static int get_hub_id_for_hub_handle(int hubHandle) { } } -static int get_hub_handle_for_app_instance(int id) { +static int get_hub_handle_for_app_instance(jint id) { if (!db.appInstances.count(id)) { - ALOGD("%s: Cannot find app for app instance %d", __FUNCTION__, id); + ALOGD("%s: Cannot find app for app instance %" PRId32, + __FUNCTION__, id); return -1; } return db.appInstances[id].hubHandle; } -static int get_hub_id_for_app_instance(int id) { +static int get_hub_id_for_app_instance(jint id) { int hubHandle = get_hub_handle_for_app_instance(id); if (hubHandle < 0) { @@ -278,7 +315,7 @@ static int get_hub_id_for_app_instance(int id) { return db.hubInfo.hubs[hubHandle].hub_id; } -static int get_app_instance_for_app_id(uint64_t app_id) { +static jint get_app_instance_for_app_id(uint64_t app_id) { auto end = db.appInstances.end(); for (auto current = db.appInstances.begin(); current != end; ++current) { if (current->second.appInfo.app_name.id == app_id) { @@ -289,9 +326,10 @@ static int get_app_instance_for_app_id(uint64_t app_id) { return -1; } -static int set_dest_app(hub_message_t *msg, int id) { +static int set_dest_app(hub_message_t *msg, jint id) { if (!db.appInstances.count(id)) { - ALOGD("%s: Cannot find app for app instance %d", __FUNCTION__, id); + ALOGD("%s: Cannot find app for app instance %" PRId32, + __FUNCTION__, id); return -1; } @@ -303,7 +341,7 @@ static void query_hub_for_apps(uint64_t appId, uint32_t hubHandle) { hub_message_t msg; query_apps_request_t queryMsg; - queryMsg.app_name.id = NANOAPP_VENDOR_ALL_APPS; + queryMsg.app_name.id = appId; msg.message_type = CONTEXT_HUB_QUERY_APPS; msg.message_len = sizeof(queryMsg); @@ -322,7 +360,7 @@ static void sendQueryForApps(uint64_t appId) { } } -static int return_id(int id) { +static int return_id(jint id) { // Note : This method is not thread safe. // id returned is guaranteed to be in use if (id >= 0) { @@ -333,9 +371,9 @@ static int return_id(int id) { return -1; } -static int generate_id() { +static jint generate_id() { // Note : This method is not thread safe. - int retVal = -1; + jint retVal = -1; if (!db.freeIds.empty()) { retVal = db.freeIds.front(); @@ -346,8 +384,8 @@ static int generate_id() { } -static int add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle, - int appInstanceHandle, JNIEnv *env) { +static jint add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle, + jint appInstanceHandle, JNIEnv *env) { ALOGI("Loading App"); @@ -355,10 +393,12 @@ static int add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle, app_instance_info_s entry; assert(appInfo); + const char *action = "Updated"; if (db.appInstances.count(appInstanceHandle) == 0) { + action = "Added"; appInstanceHandle = generate_id(); if (appInstanceHandle < 0) { - ALOGE("Cannot find resources to add app instance %d", + ALOGE("Cannot find resources to add app instance %" PRId32, appInstanceHandle); return -1; } @@ -378,36 +418,42 @@ static int add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle, hubHandle, entry.instanceId, entry.truncName, entry.appInfo.version); - ALOGW("Added App 0x%" PRIx64 " on hub Handle %" PRId32 - " as appInstance %d", entry.truncName, + ALOGW("%s App 0x%" PRIx64 " on hub Handle %" PRId32 + " as appInstance %" PRId32, action, entry.truncName, entry.hubHandle, appInstanceHandle); return appInstanceHandle; } -int delete_app_instance(int id, JNIEnv *env) { - if (!db.appInstances.count(id)) { - ALOGW("Cannot find App id : %d", id); - return -1; - } +int delete_app_instance(jint id, JNIEnv *env) { + bool fullyDeleted = true; + if (db.appInstances.count(id)) { + db.appInstances.erase(id); + } else { + ALOGW("Cannot delete App id (%" PRId32 ") from the JNI C++ cache", id); + fullyDeleted = false; + } return_id(id); - db.appInstances.erase(id); - if (env->CallIntMethod(db.jniInfo.jContextHubService, + + if ((env == nullptr) || + (env->CallIntMethod(db.jniInfo.jContextHubService, db.jniInfo.contextHubServiceDeleteAppInstance, - id) != 0) { - ALOGW("Could not delete App id : %d", id); - return -1; + id) != 0)) { + ALOGW("Cannot delete App id (%" PRId32 ") from Java cache", id); + fullyDeleted = false; } - ALOGI("Deleted App id : %d", id); - - return 0; + if (fullyDeleted) { + ALOGI("Deleted App id : %" PRId32, id); + return 0; + } + return -1; } static int startLoadAppTxn(uint64_t appId, int hubHandle) { app_instance_info_s *txnInfo = (app_instance_info_s *)malloc(sizeof(app_instance_info_s)); - int instanceId = generate_id(); + jint instanceId = generate_id(); if (!txnInfo || instanceId < 0) { return_id(instanceId); @@ -432,8 +478,8 @@ static int startLoadAppTxn(uint64_t appId, int hubHandle) { return 0; } -static int startUnloadAppTxn(uint32_t appInstanceHandle) { - uint32_t *txnData = (uint32_t *) malloc(sizeof(uint32_t)); +static int startUnloadAppTxn(jint appInstanceHandle) { + jint *txnData = (jint *) malloc(sizeof(jint)); if (!txnData) { ALOGW("Cannot allocate memory to start unload transaction"); return -1; @@ -454,7 +500,6 @@ static void initContextHubService() { int err = 0; db.hubInfo.hubs = nullptr; db.hubInfo.numHubs = 0; - int i; err = hw_get_module(CONTEXT_HUB_MODULE_ID, (hw_module_t const**)(&db.hubInfo.contextHubModule)); @@ -465,7 +510,7 @@ static void initContextHubService() { } // Prep for storing app info - for(i = MIN_APP_ID; i <= MAX_APP_ID; i++) { + for (jint i = MIN_APP_ID; i <= MAX_APP_ID; i++) { db.freeIds.push(i); } @@ -485,7 +530,7 @@ static void initContextHubService() { return; } - for (i = 0; i < db.hubInfo.numHubs; i++) { + for (int i = 0; i < db.hubInfo.numHubs; i++) { db.hubInfo.cookies[i] = db.hubInfo.hubs[i].hub_id; ALOGI("Subscribing to hubHandle %d with OS App name %" PRIu64, i, db.hubInfo.hubs[i].os_app_name.id); if (db.hubInfo.contextHubModule->subscribe_messages(db.hubInfo.hubs[i].hub_id, @@ -547,13 +592,15 @@ int handle_query_apps_response(const uint8_t *msg, int msgLen, memcpy(&info, unalignedInfoAddr, sizeof(info)); // We will only have one instance of the app // TODO : Change this logic once we support multiple instances of the same app - int appInstance = get_app_instance_for_app_id(info.app_name.id); + jint appInstance = get_app_instance_for_app_id(info.app_name.id); add_app_instance(&info, hubHandle, appInstance, env); } return 0; } +// TODO(b/30807327): Do not use raw bytes for additional data. Use the +// JNI interfaces for the appropriate types. static void passOnOsResponse(uint32_t hubHandle, uint32_t msgType, status_response_t *rsp, int8_t *additionalData, size_t additionalDataLen) { @@ -584,7 +631,32 @@ static void passOnOsResponse(uint32_t hubHandle, uint32_t msgType, header[HEADER_FIELD_HUB_HANDLE] = hubHandle; header[HEADER_FIELD_APP_INSTANCE] = OS_APP_ID; - msg[0] = rsp->result; + // Due to API constraints, at the moment we can't change the fact that + // we're changing our 4-byte response to a 1-byte value. But we can prevent + // the possible change in sign (and thus meaning) that would happen from + // a naive cast. Further, we can log when we're losing part of the value. + // TODO(b/30918279): Don't truncate this result. + int8_t truncatedResult; + bool neededToTruncate; + if (rsp->result < INT8_MIN) { + neededToTruncate = true; + truncatedResult = INT8_MIN; + } else if (rsp->result > INT8_MAX) { + neededToTruncate = true; + truncatedResult = INT8_MAX; + } else { + neededToTruncate = false; + // Since this value fits within an int8_t, this is a safe cast which + // won't change the value or sign. + truncatedResult = static_cast<int8_t>(rsp->result); + } + if (neededToTruncate) { + ALOGW("Response from Context Hub truncated. Value was %" PRId32 + ", but giving Java layer %" PRId8, + rsp->result, (int)truncatedResult); + } + + msg[0] = truncatedResult; if (additionalData) { memcpy(&msg[1], additionalData, additionalDataLen); @@ -603,6 +675,8 @@ static void passOnOsResponse(uint32_t hubHandle, uint32_t msgType, env->CallIntMethod(db.jniInfo.jContextHubService, db.jniInfo.contextHubServiceMsgReceiptCallback, jheader, jmsg); + env->DeleteLocalRef(jmsg); + env->DeleteLocalRef(jheader); delete[] msg; } @@ -613,7 +687,13 @@ void closeUnloadTxn(bool success) { if (success && fetchTxnData(&txnId, &txnData) == 0 && txnId == CONTEXT_HUB_UNLOAD_APP) { - db.appInstances.erase(*(uint32_t *)txnData); + JNIEnv *env; + if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) { + ALOGW("Could not attach to JVM !"); + env = nullptr; + } + jint handle = *reinterpret_cast<jint *>(txnData); + delete_app_instance(handle, env); } else { ALOGW("Could not unload the app successfully ! success %d, txnData %p", success, txnData); } @@ -621,7 +701,7 @@ void closeUnloadTxn(bool success) { closeTxn(); } -void closeLoadTxn(bool success, int *appInstanceHandle) { +static bool closeLoadTxn(bool success, jint *appInstanceHandle) { void *txnData; hub_messages_e txnId; @@ -635,13 +715,17 @@ void closeLoadTxn(bool success, int *appInstanceHandle) { add_app_instance(&info->appInfo, info->hubHandle, info->instanceId, env); } else { ALOGW("Could not attach to JVM !"); + success = false; } sendQueryForApps(info->appInfo.app_name.id); } else { ALOGW("Could not load the app successfully ! Unexpected failure"); + *appInstanceHandle = INVALID_APP_ID; + success = false; } closeTxn(); + return success; } static bool isValidOsStatus(const uint8_t *msg, size_t msgLen, @@ -668,6 +752,7 @@ static void invalidateNanoApps(uint32_t hubHandle) { if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) { ALOGW("Could not attach to JVM !"); + env = nullptr; } auto end = db.appInstances.end(); @@ -697,8 +782,27 @@ static int handle_os_message(uint32_t msgType, uint32_t hubHandle, case CONTEXT_HUB_UNLOAD_APP: if (isValidOsStatus(msg, msgLen, &rsp)) { if (msgType == CONTEXT_HUB_LOAD_APP) { - int appInstanceHandle; - closeLoadTxn(rsp.result == 0, &appInstanceHandle); + jint appInstanceHandle = INVALID_APP_ID; + bool appRunningOnHub = (rsp.result == 0); + if (!(closeLoadTxn(appRunningOnHub, &appInstanceHandle))) { + if (appRunningOnHub) { + // Now we're in an odd situation. Our nanoapp + // is up and running on the Context Hub. However, + // something went wrong in our Service code so that + // we're not able to properly track this nanoapp + // in our Service code. If we tell the Java layer + // things are good, it's a lie because the handle + // we give them will fail when used with the Service. + // If we tell the Java layer this failed, it's kind + // of a lie as well, since this nanoapp is running. + // + // We leave a more robust fix for later, and for + // now just tell the user things have failed. + // + // TODO(b/30835981): Make this situation better. + rsp.result = -1; + } + } passOnOsResponse(hubHandle, msgType, &rsp, (int8_t *)(&appInstanceHandle), sizeof(appInstanceHandle)); } else if (msgType == CONTEXT_HUB_UNLOAD_APP) { @@ -778,7 +882,7 @@ int context_hub_callback(uint32_t hubId, if (messageType < CONTEXT_HUB_TYPE_PRIVATE_MSG_BASE) { handle_os_message(messageType, hubHandle, (uint8_t*) msg->message, msg->message_len); } else { - int appHandle = get_app_instance_for_app_id(msg->app_name.id); + jint appHandle = get_app_instance_for_app_id(msg->app_name.id); if (appHandle < 0) { ALOGE("Filtering out message due to invalid App Instance."); } else { @@ -1051,7 +1155,8 @@ static jint nativeSendMessage(JNIEnv *env, jobject instance, jintArray header_, ALOGD("Asking HAL to remove app"); retVal = db.hubInfo.contextHubModule->send_message(hubId, &msg); } else { - ALOGD("Could not find app instance %d on hubHandle %d, setAddress %d", + ALOGD("Could not find app instance %" PRId32 " on hubHandle %" PRId32 + ", setAddress %d", header[HEADER_FIELD_APP_INSTANCE], header[HEADER_FIELD_HUB_HANDLE], (int)setAddressSuccess); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 0d8a95c9bcd3..73b3f52f078b 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -371,7 +371,12 @@ static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz, if (sur != NULL) { bufferProducer = sur->getIGraphicBufferProducer(); } - SurfaceComposerClient::setDisplaySurface(token, bufferProducer); + status_t err = SurfaceComposerClient::setDisplaySurface(token, + bufferProducer); + if (err != NO_ERROR) { + doThrowIAE(env, "Illegal Surface, could not enable async mode. Was this" + " Surface created with singleBufferMode?"); + } } static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index a04fc2a70686..3207b3c6f187 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -54,6 +54,7 @@ #include "ScopedLocalRef.h" #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" +#include "fd_utils-inl.h" #include "nativebridge/native_bridge.h" @@ -436,6 +437,9 @@ static void SetForkLoad(bool boost) { } #endif +// The list of open zygote file descriptors. +static FileDescriptorTable* gOpenFdTable = NULL; + // Utility routine to fork zygote and specialize the child process. static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids, jint debug_flags, jobjectArray javaRlimits, @@ -450,6 +454,18 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra SetForkLoad(true); #endif + // If this is the first fork for this zygote, create the open FD table. + // If it isn't, we just need to check whether the list of open files has + // changed (and it shouldn't in the normal case). + if (gOpenFdTable == NULL) { + gOpenFdTable = FileDescriptorTable::Create(); + if (gOpenFdTable == NULL) { + RuntimeAbort(env, __LINE__, "Unable to construct file descriptor table."); + } + } else if (!gOpenFdTable->Restat()) { + RuntimeAbort(env, __LINE__, "Unable to restat file descriptor table."); + } + pid_t pid = fork(); if (pid == 0) { @@ -459,6 +475,12 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra // Clean up any descriptors which must be closed immediately DetachDescriptors(env, fdsToClose); + // Re-open all remaining open file descriptors so that they aren't shared + // with the zygote across a fork. + if (!gOpenFdTable->Reopen()) { + RuntimeAbort(env, __LINE__, "Unable to reopen whitelisted descriptors."); + } + // Keep capabilities across UID change, unless we're staying root. if (uid != 0) { EnableKeepCapabilities(env); diff --git a/core/jni/fd_utils-inl.h b/core/jni/fd_utils-inl.h new file mode 100644 index 000000000000..1f34b0275384 --- /dev/null +++ b/core/jni/fd_utils-inl.h @@ -0,0 +1,448 @@ +/* + * 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. + */ + +#include <string> +#include <unordered_map> +#include <set> +#include <vector> +#include <algorithm> + +#include <dirent.h> +#include <fcntl.h> +#include <grp.h> +#include <inttypes.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <cutils/log.h> +#include "JNIHelp.h" +#include "ScopedPrimitiveArray.h" + +// Whitelist of open paths that the zygote is allowed to keep open +// that will be recreated across forks. In addition to files here, +// all files ending with ".jar" under /system/framework" are +// whitelisted. See FileDescriptorInfo::IsWhitelisted for the +// canonical definition. +static const char* kPathWhitelist[] = { + "/dev/null", + "/dev/pmsg0", + "/system/etc/event-log-tags", + "/sys/kernel/debug/tracing/trace_marker", + "/system/framework/framework-res.apk", + "/dev/urandom", + "/dev/ion" +}; + +static const char* kFdPath = "/proc/self/fd"; + +// Keeps track of all relevant information (flags, offset etc.) of an +// open zygote file descriptor. +class FileDescriptorInfo { + public: + // Create a FileDescriptorInfo for a given file descriptor. Returns + // |NULL| if an error occurred. + static FileDescriptorInfo* createFromFd(int fd) { + struct stat f_stat; + // This should never happen; the zygote should always have the right set + // of permissions required to stat all its open files. + if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { + ALOGE("Unable to stat fd %d : %s", fd, strerror(errno)); + return NULL; + } + + // Ignore (don't reopen or fail on) socket fds for now. + if (S_ISSOCK(f_stat.st_mode)) { + ALOGW("Unsupported socket FD: %d, ignoring.", fd); + return new FileDescriptorInfo(fd); + } + + // We only handle whitelisted regular files and character devices. Whitelisted + // character devices must provide a guarantee of sensible behaviour when + // reopened. + // + // S_ISDIR : Not supported. (We could if we wanted to, but it's unused). + // S_ISLINK : Not supported. + // S_ISBLK : Not supported. + // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate + // with the child process across forks but those should have been closed + // before we got to this point. + if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) { + ALOGE("Unsupported st_mode %d", f_stat.st_mode); + return NULL; + } + + std::string file_path; + if (!Readlink(fd, &file_path)) { + return NULL; + } + + if (!IsWhitelisted(file_path)) { + ALOGE("Not whitelisted : %s", file_path.c_str()); + return NULL; + } + + // File descriptor flags : currently on FD_CLOEXEC. We can set these + // using F_SETFD - we're single threaded at this point of execution so + // there won't be any races. + const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD)); + if (fd_flags == -1) { + ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno)); + return NULL; + } + + // File status flags : + // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through + // to the open() call. + // + // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can + // do about these, since the file has already been created. We shall ignore + // them here. + // + // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL + // can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK. + // In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for + // their presence and pass them in to open(). + int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL)); + if (fs_flags == -1) { + ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno)); + return NULL; + } + + // File offset : Ignore the offset for non seekable files. + const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); + + // We pass the flags that open accepts to open, and use F_SETFL for + // the rest of them. + static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC); + int open_flags = fs_flags & (kOpenFlags); + fs_flags = fs_flags & (~(kOpenFlags)); + + return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset); + } + + // Checks whether the file descriptor associated with this object + // refers to the same description. + bool Restat() const { + struct stat f_stat; + if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) { + return false; + } + + return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev; + } + + bool Reopen() const { + // Always skip over socket FDs for now. + if (is_sock) { + return true; + } + + // NOTE: This might happen if the file was unlinked after being opened. + // It's a common pattern in the case of temporary files and the like but + // we should not allow such usage from the zygote. + const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags)); + + if (new_fd == -1) { + ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) { + close(new_fd); + ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) { + close(new_fd); + ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno)); + return false; + } + + if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) { + close(new_fd); + ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno)); + return false; + } + + if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) { + close(new_fd); + ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno)); + return false; + } + + if (close(new_fd) == -1) { + ALOGW("Failed close(%d) : %s", new_fd, strerror(errno)); + } + + return true; + } + + const int fd; + const struct stat stat; + const std::string file_path; + const int open_flags; + const int fd_flags; + const int fs_flags; + const off_t offset; + const bool is_sock; + + private: + FileDescriptorInfo(int fd) : + fd(fd), + stat(), + open_flags(0), + fd_flags(0), + fs_flags(0), + offset(0), + is_sock(true) { + } + + FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags, + int fd_flags, int fs_flags, off_t offset) : + fd(fd), + stat(stat), + file_path(file_path), + open_flags(open_flags), + fd_flags(fd_flags), + fs_flags(fs_flags), + offset(offset), + is_sock(false) { + } + + // Returns true iff. a given path is whitelisted. A path is whitelisted + // if it belongs to the whitelist (see kPathWhitelist) or if it's a path + // under /system/framework that ends with ".jar". + static bool IsWhitelisted(const std::string& path) { + for (size_t i = 0; i < (sizeof(kPathWhitelist) / sizeof(kPathWhitelist[0])); ++i) { + if (kPathWhitelist[i] == path) { + return true; + } + } + + static const std::string kFrameworksPrefix = "/system/framework/"; + static const std::string kJarSuffix = ".jar"; + if (path.compare(0, kFrameworksPrefix.size(), kFrameworksPrefix) == 0 && + path.compare(path.size() - kJarSuffix.size(), kJarSuffix.size(), kJarSuffix) == 0) { + return true; + } + return false; + } + + // TODO: Call android::base::Readlink instead of copying the code here. + static bool Readlink(const int fd, std::string* result) { + char path[64]; + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + + // Code copied from android::base::Readlink starts here : + + // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer, + // and truncates to whatever size you do supply, so it can't be used to query. + // We could call lstat first, but that would introduce a race condition that + // we couldn't detect. + // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here. + char buf[4096]; + ssize_t len = readlink(path, buf, sizeof(buf)); + if (len == -1) return false; + + result->assign(buf, len); + return true; + } + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo); +}; + +// A FileDescriptorTable is a collection of FileDescriptorInfo objects +// keyed by their FDs. +class FileDescriptorTable { + public: + // Creates a new FileDescriptorTable. This function scans + // /proc/self/fd for the list of open file descriptors and collects + // information about them. Returns NULL if an error occurs. + static FileDescriptorTable* Create() { + DIR* d = opendir(kFdPath); + if (d == NULL) { + ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); + return NULL; + } + int dir_fd = dirfd(d); + dirent* e; + + std::unordered_map<int, FileDescriptorInfo*> open_fd_map; + while ((e = readdir(d)) != NULL) { + const int fd = ParseFd(e, dir_fd); + if (fd == -1) { + continue; + } + + FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd); + if (info == NULL) { + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + } + return NULL; + } + open_fd_map[fd] = info; + } + + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + return NULL; + } + return new FileDescriptorTable(open_fd_map); + } + + bool Restat() { + std::set<int> open_fds; + + // First get the list of open descriptors. + DIR* d = opendir(kFdPath); + if (d == NULL) { + ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno)); + return false; + } + + int dir_fd = dirfd(d); + dirent* e; + while ((e = readdir(d)) != NULL) { + const int fd = ParseFd(e, dir_fd); + if (fd == -1) { + continue; + } + + open_fds.insert(fd); + } + + if (closedir(d) == -1) { + ALOGE("Unable to close directory : %s", strerror(errno)); + return false; + } + + return RestatInternal(open_fds); + } + + // Reopens all file descriptors that are contained in the table. Returns true + // if all descriptors were re-opened, and false if an error occurred. + bool Reopen() { + std::unordered_map<int, FileDescriptorInfo*>::const_iterator it; + for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { + const FileDescriptorInfo* info = it->second; + if (info == NULL || !info->Reopen()) { + return false; + } + } + + return true; + } + + private: + FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map) + : open_fd_map_(map) { + } + + bool RestatInternal(std::set<int>& open_fds) { + bool error = false; + + // Iterate through the list of file descriptors we've already recorded + // and check whether : + // + // (a) they continue to be open. + // (b) they refer to the same file. + std::unordered_map<int, FileDescriptorInfo*>::iterator it; + for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) { + std::set<int>::const_iterator element = open_fds.find(it->first); + if (element == open_fds.end()) { + // The entry from the file descriptor table is no longer in the list + // of open files. We warn about this condition and remove it from + // the list of FDs under consideration. + // + // TODO(narayan): This will be an error in a future android release. + // error = true; + ALOGW("Zygote closed file descriptor %d.", it->first); + open_fd_map_.erase(it); + } else { + // The entry from the file descriptor table is still open. Restat + // it and check whether it refers to the same file. + open_fds.erase(element); + const bool same_file = it->second->Restat(); + if (!same_file) { + // The file descriptor refers to a different description. We must + // update our entry in the table. + delete it->second; + it->second = FileDescriptorInfo::createFromFd(*element); + if (it->second == NULL) { + // The descriptor no longer no longer refers to a whitelisted file. + // We flag an error and remove it from the list of files we're + // tracking. + error = true; + open_fd_map_.erase(it); + } + } else { + // It's the same file. Nothing to do here. + } + } + } + + if (open_fds.size() > 0) { + // The zygote has opened new file descriptors since our last inspection. + // We warn about this condition and add them to our table. + // + // TODO(narayan): This will be an error in a future android release. + // error = true; + ALOGW("Zygote opened %zd new file descriptor(s).", open_fds.size()); + + // TODO(narayan): This code will be removed in a future android release. + std::set<int>::const_iterator it; + for (it = open_fds.begin(); it != open_fds.end(); ++it) { + const int fd = (*it); + FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd); + if (info == NULL) { + // A newly opened file is not on the whitelist. Flag an error and + // continue. + error = true; + } else { + // Track the newly opened file. + open_fd_map_[fd] = info; + } + } + } + + return !error; + } + + static int ParseFd(dirent* e, int dir_fd) { + char* end; + const int fd = strtol(e->d_name, &end, 10); + if ((*end) != '\0') { + return -1; + } + + // Don't bother with the standard input/output/error, they're handled + // specially post-fork anyway. + if (fd <= STDERR_FILENO || fd == dir_fd) { + return -1; + } + + return fd; + } + + // Invariant: All values in this unordered_map are non-NULL. + std::unordered_map<int, FileDescriptorInfo*> open_fd_map_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable); +}; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ed71fc2f4a8e..6c6fd9005083 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -183,6 +183,7 @@ <protected-broadcast android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.btopp.intent.action.INCOMING_FILE_NOTIFICATION" /> <protected-broadcast android:name="android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT" /> <protected-broadcast android:name="android.btopp.intent.action.LIST" /> @@ -199,6 +200,8 @@ <protected-broadcast android:name="com.android.bluetooth.pbap.userconfirmtimeout" /> <protected-broadcast android:name="com.android.bluetooth.pbap.authresponse" /> <protected-broadcast android:name="com.android.bluetooth.pbap.authcancelled" /> + <protected-broadcast android:name="com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT" /> + <protected-broadcast android:name="com.android.bluetooth.sap.action.DISCONNECT_ACTION" /> <protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" /> @@ -394,6 +397,8 @@ <protected-broadcast android:name="android.bluetooth.adapter.action.BLE_STATE_CHANGED" /> <protected-broadcast android:name="com.android.bluetooth.map.USER_CONFIRM_TIMEOUT" /> + <protected-broadcast android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" /> + <protected-broadcast android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" /> <protected-broadcast android:name="android.content.jobscheduler.JOB_DELAY_EXPIRED" /> <protected-broadcast android:name="android.content.syncmanager.SYNC_ALARM" /> <protected-broadcast android:name="android.media.INTERNAL_RINGER_MODE_CHANGED_ACTION" /> @@ -1308,6 +1313,7 @@ android:protectionLevel="dangerous" android:description="@string/permdesc_getAccounts" android:label="@string/permlab_getAccounts" /> + <uses-permission android:name="android.permission.GET_ACCOUNTS"/> <!-- @SystemApi Allows applications to call into AccountAuthenticators. <p>Not for use by third-party applications. --> @@ -1628,7 +1634,7 @@ <!-- @hide Allows an application to create, remove users and get the list of users on the device. Applications holding this permission can only create restricted, - guest, managed, and ephemeral users. For creating other kind of users, + guest, managed, demo, and ephemeral users. For creating other kind of users, {@link android.Manifest.permission#MANAGE_USERS} is needed. This permission is not available to third party applications. --> <permission android:name="android.permission.CREATE_USERS" diff --git a/core/res/res/drawable/ic_refresh.xml b/core/res/res/drawable/ic_refresh.xml index 1f671684861f..1297407fbb8b 100644 --- a/core/res/res/drawable/ic_refresh.xml +++ b/core/res/res/drawable/ic_refresh.xml @@ -21,7 +21,4 @@ Copyright (C) 2016 The Android Open Source Project <path android:fillColor="#FF000000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4.0 12.0,4.0c-4.42,0.0 -7.99,3.58 -7.99,8.0s3.57,8.0 7.99,8.0c3.73,0.0 6.84,-2.55 7.73,-6.0l-2.08,0.0c-0.82,2.33 -3.04,4.0 -5.65,4.0 -3.31,0.0 -6.0,-2.69 -6.0,-6.0s2.69,-6.0 6.0,-6.0c1.66,0.0 3.1,0.69 4.22,1.78L13.0,11.0l7.0,0.0L20.0,4.0l-2.35,2.35z"/> - <path - android:pathData="M0 0h24v24H0z" - android:fillColor="#00000000"/> </vector> diff --git a/core/res/res/drawable/progress_indeterminate_anim_large_material.xml b/core/res/res/drawable/progress_indeterminate_anim_large_material.xml new file mode 100644 index 000000000000..560ec5af508f --- /dev/null +++ b/core/res/res/drawable/progress_indeterminate_anim_large_material.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/vector_drawable_progress_bar_large" > + <target + android:name="progressBar" + android:animation="@anim/progress_indeterminate_material" /> + + <target + android:name="root" + android:animation="@anim/progress_indeterminate_rotation_material" /> +</animated-vector> + diff --git a/core/res/res/drawable/progress_indeterminate_anim_medium_material.xml b/core/res/res/drawable/progress_indeterminate_anim_medium_material.xml new file mode 100644 index 000000000000..fbea22f443e1 --- /dev/null +++ b/core/res/res/drawable/progress_indeterminate_anim_medium_material.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/vector_drawable_progress_bar_medium" > + + <target + android:name="progressBar" + android:animation="@anim/progress_indeterminate_material" /> + + <target + android:name="root" + android:animation="@anim/progress_indeterminate_rotation_material" /> + +</animated-vector>
\ No newline at end of file diff --git a/core/res/res/drawable/progress_large_material.xml b/core/res/res/drawable/progress_large_material.xml index 526f9141d716..ee82e35d00f5 100644 --- a/core/res/res/drawable/progress_large_material.xml +++ b/core/res/res/drawable/progress_large_material.xml @@ -13,16 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/vector_drawable_progress_bar_large" > - - <target - android:name="progressBar" - android:animation="@anim/progress_indeterminate_material" /> - - <target - android:name="root" - android:animation="@anim/progress_indeterminate_rotation_material" /> - -</animated-vector> +<com.android.internal.graphics.drawable.AnimationScaleListDrawable + xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/progress_static_material" /> + <item android:drawable="@drawable/progress_indeterminate_anim_large_material" /> +</com.android.internal.graphics.drawable.AnimationScaleListDrawable> diff --git a/core/res/res/drawable/progress_medium_material.xml b/core/res/res/drawable/progress_medium_material.xml index cc35816c9c3c..5c9260069e5d 100644 --- a/core/res/res/drawable/progress_medium_material.xml +++ b/core/res/res/drawable/progress_medium_material.xml @@ -13,15 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/vector_drawable_progress_bar_medium" > - - <target - android:name="progressBar" - android:animation="@anim/progress_indeterminate_material" /> - - <target - android:name="root" - android:animation="@anim/progress_indeterminate_rotation_material" /> - -</animated-vector>
\ No newline at end of file +<com.android.internal.graphics.drawable.AnimationScaleListDrawable + xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/progress_static_material" /> + <item android:drawable="@drawable/progress_indeterminate_anim_medium_material" /> +</com.android.internal.graphics.drawable.AnimationScaleListDrawable> diff --git a/core/res/res/drawable/progress_small_material.xml b/core/res/res/drawable/progress_small_material.xml index c6e43801cdf7..cec9d9565183 100644 --- a/core/res/res/drawable/progress_small_material.xml +++ b/core/res/res/drawable/progress_small_material.xml @@ -13,16 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. --> - -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/vector_drawable_progress_bar_small" > - - <target - android:name="progressBar" - android:animation="@anim/progress_indeterminate_material" /> - - <target - android:name="root" - android:animation="@anim/progress_indeterminate_rotation_material" /> - -</animated-vector> +<com.android.internal.graphics.drawable.AnimationScaleListDrawable + xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/progress_static_material" /> + <item> + <animated-vector android:drawable="@drawable/vector_drawable_progress_bar_small" > + <target + android:name="progressBar" + android:animation="@anim/progress_indeterminate_material" /> + <target + android:name="root" + android:animation="@anim/progress_indeterminate_rotation_material" /> + </animated-vector> + </item> +</com.android.internal.graphics.drawable.AnimationScaleListDrawable> diff --git a/core/res/res/drawable/progress_static_material.xml b/core/res/res/drawable/progress_static_material.xml new file mode 100644 index 000000000000..b078fa93a155 --- /dev/null +++ b/core/res/res/drawable/progress_static_material.xml @@ -0,0 +1,25 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + > + <path + android:fillColor="?attr/colorControlActivated" + android:pathData="M17.65,6.35C16.2,4.9 14.21,4.0 12.0,4.0c-4.42,0.0 -7.99,3.58 -7.99,8.0s3.57,8.0 7.99,8.0c3.73,0.0 6.84,-2.55 7.73,-6.0l-2.08,0.0c-0.82,2.33 -3.04,4.0 -5.65,4.0 -3.31,0.0 -6.0,-2.69 -6.0,-6.0s2.69,-6.0 6.0,-6.0c1.66,0.0 3.1,0.69 4.22,1.78L13.0,11.0l7.0,0.0L20.0,4.0l-2.35,2.35z"/> +</vector> diff --git a/core/res/res/values-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml index 8023edffdb47..350caaab8fa6 100644 --- a/core/res/res/values-gl-rES/strings.xml +++ b/core/res/res/values-gl-rES/strings.xml @@ -598,7 +598,7 @@ <string name="phoneTypeCallback" msgid="2712175203065678206">"Devolver chamada"</string> <string name="phoneTypeCar" msgid="8738360689616716982">"Coche"</string> <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Empresa (ppal.)"</string> - <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string> + <string name="phoneTypeIsdn" msgid="8022453193171370337">"RDSI"</string> <string name="phoneTypeMain" msgid="6766137010628326916">"Principal"</string> <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Outro fax"</string> <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index bebe027cb67f..10c583637f59 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1362,7 +1362,7 @@ <string name="action_menu_overflow_description" msgid="2295659037509008453">"אפשרויות נוספות"</string> <string name="action_bar_home_description_format" msgid="7965984360903693903">"%1$s, %2$s"</string> <string name="action_bar_home_subtitle_description_format" msgid="6985546530471780727">"%1$s, %2$s, %3$s"</string> - <string name="storage_internal" msgid="3570990907910199483">"אחסון משותף פנימי"</string> + <string name="storage_internal" msgid="3570990907910199483">"אחסון שיתוף פנימי"</string> <string name="storage_sd_card" msgid="3282948861378286745">"כרטיס SD"</string> <string name="storage_sd_card_label" msgid="6347111320774379257">"כרטיס SD של <xliff:g id="MANUFACTURER">%s</xliff:g>"</string> <string name="storage_usb_drive" msgid="6261899683292244209">"כונן USB"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 5b446b445a0f..8bd7160d16b8 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1310,7 +1310,7 @@ <string name="action_menu_overflow_description" msgid="2295659037509008453">"옵션 더보기"</string> <string name="action_bar_home_description_format" msgid="7965984360903693903">"%1$s, %2$s"</string> <string name="action_bar_home_subtitle_description_format" msgid="6985546530471780727">"%1$s, %2$s, %3$s"</string> - <string name="storage_internal" msgid="3570990907910199483">"내부 공유 저장공간"</string> + <string name="storage_internal" msgid="3570990907910199483">"내부 공유 저장용량"</string> <string name="storage_sd_card" msgid="3282948861378286745">"SD 카드"</string> <string name="storage_sd_card_label" msgid="6347111320774379257">"<xliff:g id="MANUFACTURER">%s</xliff:g> SD 카드"</string> <string name="storage_usb_drive" msgid="6261899683292244209">"USB 드라이브"</string> diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml index 0f82dcbdffcf..58c2c8a2f219 100644 --- a/core/res/res/values-ky-rKG/strings.xml +++ b/core/res/res/values-ky-rKG/strings.xml @@ -1197,8 +1197,7 @@ <string name="permdesc_readInstallSessions" msgid="2049771699626019849">"Колдонмого орнотуу сеанстарын окуу мүмкүнчүлүгүн берет. Ушуну менен, ал жигердүү топтом орнотууларынын чоо-жайын көрө алат."</string> <string name="permlab_requestInstallPackages" msgid="5782013576218172577">"орнотуу топтомдорун суроо"</string> <string name="permdesc_requestInstallPackages" msgid="5740101072486783082">"Колдонмо топтомдорду орнотууга уруксат сурай алат."</string> - <!-- no translation found for tutorial_double_tap_to_zoom_message_short (1311810005957319690) --> - <skip /> + <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Масштабдын параметрлерин өзгөртүү үчүн бул жерди эки жолу басыңыз."</string> <string name="gadget_host_error_inflating" msgid="4882004314906466162">"Виджетти кошуу мүмкүн болбоду."</string> <string name="ime_action_go" msgid="8320845651737369027">"Өтүү"</string> <string name="ime_action_search" msgid="658110271822807811">"Издөө"</string> @@ -1229,10 +1228,8 @@ <string name="notification_ranker_binding_label" msgid="774540592299064747">"Эскертмелердин маанилүүлүгүн баалоо кызматы"</string> <string name="vpn_title" msgid="19615213552042827">"VPN иштетилди"</string> <string name="vpn_title_long" msgid="6400714798049252294">"VPN <xliff:g id="APP">%s</xliff:g> аркылуу жандырылды"</string> - <!-- no translation found for vpn_text (1610714069627824309) --> - <skip /> - <!-- no translation found for vpn_text_long (4907843483284977618) --> - <skip /> + <string name="vpn_text" msgid="1610714069627824309">"Тармактын параметрлерин өзгөртүү үчүн бул жерди басыңыз."</string> + <string name="vpn_text_long" msgid="4907843483284977618">"<xliff:g id="SESSION">%s</xliff:g> сеансына туташуу ишке ашты. Желенин параметрлерин өзгөртүү үчүн бул жерди басыңыз."</string> <string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Дайым иштеген VPN туташууда…"</string> <string name="vpn_lockdown_connected" msgid="8202679674819213931">"Дайым иштеген VPN туташтырылды"</string> <string name="vpn_lockdown_error" msgid="6009249814034708175">"Дайым иштеген VPN\'де ката кетти"</string> diff --git a/core/res/res/values-mcc001/config.xml b/core/res/res/values-mcc001/config.xml new file mode 100644 index 000000000000..93cde03ba6a0 --- /dev/null +++ b/core/res/res/values-mcc001/config.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 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. +*/ +--> +<resources> + <bool name="config_use_sim_language_file">true</bool> +</resources> diff --git a/core/res/res/values-mcc222-mnc10/config.xml b/core/res/res/values-mcc222-mnc10/config.xml index cd6e8c6f46c2..c819de2ba43c 100644 --- a/core/res/res/values-mcc222-mnc10/config.xml +++ b/core/res/res/values-mcc222-mnc10/config.xml @@ -28,29 +28,4 @@ <string-array translatable="false" name="config_tether_apndata"> <item>Tethering Internet,web.omnitel.it,,,,,,,,,222,10,,DUN</item> </string-array> - - <string-array translatable="false" name="config_operatorConsideredNonRoaming"> - <item>21401</item> - <item>21402</item> - <item>21403</item> - <item>21404</item> - <item>21405</item> - <item>21406</item> - <item>21407</item> - <item>21408</item> - <item>21409</item> - <item>21410</item> - <item>21411</item> - <item>21412</item> - <item>21413</item> - <item>21414</item> - <item>21415</item> - <item>21416</item> - <item>21417</item> - <item>21418</item> - <item>21419</item> - <item>21420</item> - <item>21421</item> - </string-array> - </resources> diff --git a/core/res/res/values-mcc232-mnc10/config.xml b/core/res/res/values-mcc232-mnc10/config.xml new file mode 100644 index 000000000000..bdf83016d18e --- /dev/null +++ b/core/res/res/values-mcc232-mnc10/config.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + ** Copyright 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. + */ +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Don't use roaming icon for considered operators --> + <string-array translatable="false" name="config_operatorConsideredNonRoaming"> + <item>23203</item> + <item>23205</item> + </string-array> +</resources> diff --git a/core/res/res/values-mcc232-mnc13/config.xml b/core/res/res/values-mcc232-mnc13/config.xml new file mode 100644 index 000000000000..2c14f87374f6 --- /dev/null +++ b/core/res/res/values-mcc232-mnc13/config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + ** Copyright 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. + */ +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Don't use roaming icon for considered operators --> + <string-array translatable="false" name="config_operatorConsideredNonRoaming"> + <item>23203</item> + </string-array> +</resources> diff --git a/core/res/res/values-mcc302-mnc500/config.xml b/core/res/res/values-mcc302-mnc500/config.xml new file mode 100644 index 000000000000..77f64199a80b --- /dev/null +++ b/core/res/res/values-mcc302-mnc500/config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + ** Copyright 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. + */ +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Don't use roaming icon for considered operators --> + <string-array translatable="false" name="config_operatorConsideredNonRoaming"> + <item>302</item> + </string-array> +</resources> diff --git a/core/res/res/values-mcc302-mnc510/config.xml b/core/res/res/values-mcc302-mnc510/config.xml new file mode 100644 index 000000000000..77f64199a80b --- /dev/null +++ b/core/res/res/values-mcc302-mnc510/config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + ** Copyright 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. + */ +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Don't use roaming icon for considered operators --> + <string-array translatable="false" name="config_operatorConsideredNonRoaming"> + <item>302</item> + </string-array> +</resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index ae115d877588..5a356d54b456 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1310,7 +1310,7 @@ <string name="action_menu_overflow_description" msgid="2295659037509008453">"Mais opções"</string> <string name="action_bar_home_description_format" msgid="7965984360903693903">"%1$s, %2$s"</string> <string name="action_bar_home_subtitle_description_format" msgid="6985546530471780727">"%1$s, %2$s, %3$s"</string> - <string name="storage_internal" msgid="3570990907910199483">"Armazenamento interno partilhado"</string> + <string name="storage_internal" msgid="3570990907910199483">"Armazen. interno partilhado"</string> <string name="storage_sd_card" msgid="3282948861378286745">"Cartão SD"</string> <string name="storage_sd_card_label" msgid="6347111320774379257">"Cartão SD <xliff:g id="MANUFACTURER">%s</xliff:g>"</string> <string name="storage_usb_drive" msgid="6261899683292244209">"Unidade USB"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 48e420198ffa..0a96ba399396 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5313,6 +5313,21 @@ i <attr name="alpha" /> </declare-styleable> + <!-- Drawable used to render according to the animation scale. Esp. when it is 0 due to battery + saver mode. It should contain one animatable drawable and one static drawable. + @hide --> + <declare-styleable name="AnimationScaleListDrawable"> + </declare-styleable> + + <!-- Attributes that can be assigned to a AnimationScaleListDrawable item. + @hide --> + <declare-styleable name="AnimationScaleListDrawableItem"> + <!-- Reference to a drawable resource to use for the state. If not + given, the drawable must be defined by the first child tag. --> + <attr name="drawable" /> + </declare-styleable> + + <!-- Drawable used to render a geometric shape, with a gradient or a solid color. --> <declare-styleable name="GradientDrawable"> <!-- Indicates whether the drawable should intially be visible. --> @@ -5342,7 +5357,10 @@ i <attr name="innerRadius" format="dimension" /> <!-- Thickness of the ring. When defined, thicknessRatio is ignored. --> <attr name="thickness" format="dimension" /> - <!-- Indicates whether the drawable's level affects the way the gradient is drawn. --> + <!-- Whether the drawable level value (see + {@link android.graphics.drawable.Drawable#getLevel()}) is used to scale the shape. + Scaling behavior depends on the shape type. For "ring", the angle is scaled from 0 to + 360. For all other types, there is no effect. The default value is true. --> <attr name="useLevel" /> <!-- If set, specifies the color to apply to the drawable as a tint. By default, no tint is applied. May be a color state list. --> @@ -5376,28 +5394,37 @@ i <declare-styleable name="GradientDrawableGradient"> <!-- Start color of the gradient. --> <attr name="startColor" format="color" /> - <!-- Optional center color. For linear gradients, use centerX or centerY - to place the center color. --> + <!-- Optional center color. For linear gradients, use centerX or centerY to place the center + color. --> <attr name="centerColor" format="color" /> <!-- End color of the gradient. --> <attr name="endColor" format="color" /> + <!-- Whether the drawable level value (see + {@link android.graphics.drawable.Drawable#getLevel()}) is used to scale the gradient. + Scaling behavior varies based on gradient type. For "linear", adjusts the ending + position along the gradient's axis of orientation. For "radial", adjusts the outer + radius. For "sweep", adjusts the ending angle. The default value is false. --> <attr name="useLevel" format="boolean" /> - <!-- Angle of the gradient. --> + <!-- Angle of the gradient, used only with linear gradient. Must be a multiple of 45 in the + range [0, 315]. --> <attr name="angle" format="float" /> <!-- Type of gradient. The default type is linear. --> <attr name="type"> - <!-- Linear gradient. --> + <!-- Linear gradient extending across the center point. --> <enum name="linear" value="0" /> - <!-- Radial, or circular, gradient. --> + <!-- Radial gradient extending from the center point outward. --> <enum name="radial" value="1" /> - <!-- Sweep, or angled or diamond, gradient. --> + <!-- Sweep (or angular) gradient sweeping counter-clockwise around the center point. --> <enum name="sweep" value="2" /> </attr> - <!-- X coordinate of the origin of the gradient within the shape. --> + <!-- X-position of the center point of the gradient within the shape as a fraction of the + width. The default value is 0.5. --> <attr name="centerX" format="float|fraction" /> - <!-- Y coordinate of the origin of the gradient within the shape. --> + <!-- Y-position of the center point of the gradient within the shape as a fraction of the + height. The default value is 0.5. --> <attr name="centerY" format="float|fraction" /> - <!-- Radius of the gradient, used only with radial gradient. --> + <!-- Radius of the gradient, used only with radial gradient. May be an explicit dimension + or a fractional value relative to the shape's minimum dimension. --> <attr name="gradientRadius" format="float|fraction|dimension" /> </declare-styleable> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b80b0a70a036..55a87ee202df 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2153,6 +2153,11 @@ <!-- Flag specifying whether VT is available on device --> <bool name="config_device_vt_available">false</bool> + <!-- Flag specifying whether the device will use the "allow_hold_in_ims_call" carrier config + option. When false, the device will support holding of IMS calls, regardless of the + carrier config setting. --> + <bool name="config_device_respects_hold_carrier_config">true</bool> + <!-- Flag specifying whether VT should be available for carrier: independent of carrier provisioning. If false: hard disabled. If true: then depends on carrier provisioning, availability etc --> @@ -2295,8 +2300,8 @@ <!-- An array of CDMA roaming indicators which means international roaming --> <integer-array translatable="false" name="config_cdma_international_roaming_indicators" /> - <!-- set the system language as value of EF LI/EF PL --> - <bool name="config_use_sim_language_file">true</bool> + <!-- flag to indicate if EF LI/EF PL should be used for system language --> + <bool name="config_use_sim_language_file">false</bool> <!-- Use ERI text for network name on CDMA LTE --> <bool name="config_LTE_eri_for_network_name">true</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 924d33809124..67ab8cbe0e16 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1942,6 +1942,7 @@ <java-symbol type="anim" name="lock_screen_behind_enter_fade_in" /> <java-symbol type="anim" name="lock_screen_wallpaper_exit" /> <java-symbol type="anim" name="launch_task_behind_source" /> + <java-symbol type="anim" name="wallpaper_open_exit" /> <java-symbol type="bool" name="config_alwaysUseCdmaRssi" /> <java-symbol type="dimen" name="status_bar_icon_size" /> @@ -2211,6 +2212,7 @@ <java-symbol type="bool" name="config_carrier_volte_provisioned" /> <java-symbol type="bool" name="config_carrier_volte_tty_supported" /> <java-symbol type="bool" name="config_device_vt_available" /> + <java-symbol type="bool" name="config_device_respects_hold_carrier_config" /> <java-symbol type="bool" name="config_carrier_vt_available" /> <java-symbol type="bool" name="config_device_wfc_ims_available" /> <java-symbol type="bool" name="config_carrier_wfc_ims_available" /> diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml index aefe1c1f1ab5..c68937877f85 100644 --- a/docs/html/_redirects.yaml +++ b/docs/html/_redirects.yaml @@ -1219,6 +1219,8 @@ redirects: to: /studio/intro/index.html?utm_medium=android-studio - from: /r/studio-ui/menu-start.html to: /training/index.html?utm_medium=android-studio +- from: /r/studio-ui/run-with-work-profile.html + to: /studio/run/index.html#ir-work-profile?utm_medium=android-studio # Redirects from (removed) N Preview documentation - from: /preview/features/afw.html diff --git a/docs/html/guide/topics/media/camera.jd b/docs/html/guide/topics/media/camera.jd index c806c886a3cc..4995a13dda91 100644 --- a/docs/html/guide/topics/media/camera.jd +++ b/docs/html/guide/topics/media/camera.jd @@ -9,12 +9,7 @@ page.tags=photo,video,picture,mediarecorder <li><a href="#considerations">Considerations</a></li> <li><a href="#basics">The Basics</a> <li><a href="#manifest">Manifest Declarations</a></li> - <li><a href="#intents">Using Existing Camera Apps</a> - <ol> - <li><a href="#intent-image">Image capture intent</a></li> - <li><a href="#intent-video">Video capture intent</a></li> - <li><a href="#intent-receive">Receiving camera intent result</a></li> - </ol> + <li><a href="#camera-apps">Using Existing Camera Apps</a> <li><a href="#custom-camera">Building a Camera App</a> <ol> <li><a href="#detect-camera">Detecting camera hardware</a></li> @@ -72,7 +67,7 @@ manifest</a>.</li> <li><strong>Quick Picture or Customized Camera</strong> - How will your application use the camera? Are you just interested in snapping a quick picture or video clip, or will your application provide a new way to use cameras? For a getting a quick snap or clip, consider -<a href="#intents">Using Existing Camera Apps</a>. For developing a customized camera feature, check +<a href="#camera-apps">Using Existing Camera Apps</a>. For developing a customized camera feature, check out the <a href="#custom-camera">Building a Camera App</a> section.</li> <li><strong>Storage</strong> - Are the images or videos your application generates intended to be @@ -122,8 +117,9 @@ camera. <pre> <uses-permission android:name="android.permission.CAMERA" /> </pre> - <p class="note"><strong>Note:</strong> If you are using the camera <a href="#intents">via an -intent</a>, your application does not need to request this permission.</p> + <p class="note"><strong>Note:</strong> If you are using the camera <a href="#camera-apps">by +invoking an existing camera app</a>, +your application does not need to request this permission.</p> </li> <li><strong>Camera Features</strong> - Your application must also declare use of camera features, for example: @@ -169,193 +165,17 @@ information, you must request location permission: </ul> -<h2 id="intents">Using Existing Camera Apps</h2> +<h2 id="camera-apps">Using Existing Camera Apps</h2> <p>A quick way to enable taking pictures or videos in your application without a lot of extra code -is to use an {@link android.content.Intent} to invoke an existing Android camera application. A -camera intent makes a request to capture a picture or video clip through an existing camera app and -then returns control back to your application. This section shows you how to capture an image or -video using this technique.</p> - -<p>The procedure for invoking a camera intent follows these general steps:</p> - -<ol> - <li><strong>Compose a Camera Intent</strong> - Create an {@link android.content.Intent} that -requests an image or video, using one of these intent types: - <ul> - <li>{@link android.provider.MediaStore#ACTION_IMAGE_CAPTURE MediaStore.ACTION_IMAGE_CAPTURE} - -Intent action type for requesting an image from an existing camera application.</li> - <li>{@link android.provider.MediaStore#ACTION_VIDEO_CAPTURE MediaStore.ACTION_VIDEO_CAPTURE} - -Intent action type for requesting a video from an existing camera application. </li> - </ul> - </li> - <li><strong>Start the Camera Intent</strong> - Use the {@link -android.app.Activity#startActivityForResult(android.content.Intent, int) startActivityForResult()} -method to execute the camera intent. After you start the intent, the Camera application user -interface appears on the device screen and the user can take a picture or video.</li> - <li><strong>Receive the Intent Result</strong> - Set up an {@link -android.app.Activity#onActivityResult(int, int, android.content.Intent) onActivityResult()} method -in your application to receive the callback and data from the camera intent. When the user -finishes taking a picture or video (or cancels the operation), the system calls this method.</li> -</ol> - - -<h3 id="intent-image">Image capture intent</h3> -<p>Capturing images using a camera intent is quick way to enable your application to take pictures -with minimal coding. An image capture intent can include the following extra information:</p> - -<ul> - <li>{@link android.provider.MediaStore#EXTRA_OUTPUT MediaStore.EXTRA_OUTPUT} - This setting -requires a {@link android.net.Uri} object specifying a path and file name where you'd like to -save the picture. This setting is optional but strongly recommended. If you do not specify this -value, the camera application saves the requested picture in the default location with a default -name, specified in the returned intent's {@link android.content.Intent#getData() Intent.getData()} -field.</li> -</ul> - -<p>The following example demonstrates how to construct a image capture intent and execute it. -The {@code getOutputMediaFileUri()} method in this example refers to the sample code shown in <a -href= "#saving-media">Saving Media Files</a>.</p> - -<pre> -private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100; -private Uri fileUri; - -@Override -public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - // create Intent to take a picture and return control to the calling application - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - - fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image - intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name - - // start the image capture Intent - startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE); -} -</pre> - -<p>When the {@link android.app.Activity#startActivityForResult(android.content.Intent, int) -startActivityForResult()} method is executed, users see a camera application interface. -After the user finishes taking a picture (or cancels the operation), the user interface returns to -your application, and you must intercept the {@link -android.app.Activity#onActivityResult(int, int, android.content.Intent) onActivityResult()} -method to receive the result of the intent and continue your application execution. For information -on how to receive the completed intent, see <a href="#intent-receive">Receiving camera intent -result</a>.</p> - - -<h3 id="intent-video">Video capture intent</h3> -<p>Capturing video using a camera intent is a quick way to enable your application to take videos -with minimal coding. A video capture intent can include the following extra information:</p> - -<ul> - <li>{@link android.provider.MediaStore#EXTRA_OUTPUT MediaStore.EXTRA_OUTPUT} - This setting -requires a {@link android.net.Uri} specifying a path and file name where you'd like to save the -video. This setting is optional but strongly recommended. If you do not specify this value, the -Camera application saves the requested video in the default location with a default name, specified -in the returned intent's {@link android.content.Intent#getData() Intent.getData()} field.</li> - <li>{@link android.provider.MediaStore#EXTRA_VIDEO_QUALITY MediaStore.EXTRA_VIDEO_QUALITY} - -This value can be 0 for lowest quality and smallest file size or 1 for highest quality and -larger file size.</li> - <li>{@link android.provider.MediaStore#EXTRA_DURATION_LIMIT MediaStore.EXTRA_DURATION_LIMIT} - -Set this value to limit the length, in seconds, of the video being captured.</li> - <li>{@link android.provider.MediaStore#EXTRA_SIZE_LIMIT MediaStore.EXTRA_SIZE_LIMIT} - -Set this value to limit the file size, in bytes, of the video being captured. -</li> -</ul> - -<p>The following example demonstrates how to construct a video capture intent and execute it. -The {@code getOutputMediaFileUri()} method in this example refers to the sample code shown in <a -href= "#saving-media">Saving Media Files</a>.</p> - -<pre> -private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200; -private Uri fileUri; - -@Override -public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - //create new Intent - Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); - - fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO); // create a file to save the video - intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name - - intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // set the video image quality to high - - // start the Video Capture Intent - startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE); -} -</pre> - -<p>When the {@link -android.app.Activity#startActivityForResult(android.content.Intent, int) -startActivityForResult()} method is executed, users see a modified camera application interface. -After the user finishes taking a video (or cancels the operation), the user interface -returns to your application, and you must intercept the {@link -android.app.Activity#onActivityResult(int, int, android.content.Intent) onActivityResult()} -method to receive the result of the intent and continue your application execution. For information -on how to receive the completed intent, see the next section.</p> - -<h3 id="intent-receive">Receiving camera intent result</h3> -<p>Once you have constructed and executed an image or video camera intent, your application must be -configured to receive the result of the intent. This section shows you how to intercept the callback -from a camera intent so your application can do further processing of the captured image or -video.</p> - -<p>In order to receive the result of an intent, you must override the {@link -android.app.Activity#onActivityResult(int, int, android.content.Intent) onActivityResult()} in the -activity that started the intent. The following example demonstrates how to override {@link -android.app.Activity#onActivityResult(int, int, android.content.Intent) onActivityResult()} to -capture the result of the <a href="#intent-image">image camera intent</a> or <a -href="#intent-video">video camera intent</a> examples shown in the previous sections.</p> - -<pre> -private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100; -private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200; - -@Override -protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - // Image captured and saved to fileUri specified in the Intent - Toast.makeText(this, "Image saved to:\n" + - data.getData(), Toast.LENGTH_LONG).show(); - } else if (resultCode == RESULT_CANCELED) { - // User cancelled the image capture - } else { - // Image capture failed, advise user - } - } - - if (requestCode == CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - // Video captured and saved to fileUri specified in the Intent - Toast.makeText(this, "Video saved to:\n" + - data.getData(), Toast.LENGTH_LONG).show(); - } else if (resultCode == RESULT_CANCELED) { - // User cancelled the video capture - } else { - // Video capture failed, advise user - } - } -} -</pre> - -<p>Once your activity receives a successful result, the captured image or video is available in the -specified location for your application to access.</p> - - +is to use an {@link android.content.Intent} to invoke an existing Android camera application. +The details are described in the training lessons +<a href="{@docRoot}training/camera/photobasics.html">Taking Photos Simply</a> and +<a href="{@docRoot}training/camera/videobasics.html">Recording Videos Simply</a>.</p> <h2 id="custom-camera">Building a Camera App</h2> <p>Some developers may require a camera user interface that is customized to the look of their -application or provides special features. Creating a customized camera activity requires more -code than <a href="#intents">using an intent</a>, but it can provide a more compelling experience -for your users.</p> +application or provides special features. Writing your own picture-taking code +can provide a more compelling experience for your users.</p> <p><strong> Note: The following guide is for the older, deprecated {@link android.hardware.Camera} API. For new or advanced camera applications, the newer {@link android.hardware.camera2} API is @@ -419,7 +239,7 @@ android.hardware.Camera#getNumberOfCameras() Camera.getNumberOfCameras()} method <h3 id="access-camera">Accessing cameras</h3> <p>If you have determined that the device on which your application is running has a camera, you must request to access it by getting an instance of {@link android.hardware.Camera} (unless you -are using an <a href="#intents">intent to access the camera</a>). </p> +are using an <a href="camera-apps">intent to access the camera</a>). </p> <p>To access the primary camera, use the {@link android.hardware.Camera#open() Camera.open()} method and be sure to catch any exceptions, as shown in the code below:</p> diff --git a/docs/html/work/guide.jd b/docs/html/work/guide.jd index 30b895bdb66b..b2be94990ff1 100644 --- a/docs/html/work/guide.jd +++ b/docs/html/work/guide.jd @@ -412,6 +412,17 @@ page.image=images/work/cards/android-studio_600px.png </li> </ol> +<p class="caution"><b>Caution</b>: When running your app with Instant Run in +Android Studio, attempting to open your app with a Work profile or secondary +profile will crash your app. To use your app with the Work profile, we +recommend you create a new <a href="/studio/run/rundebugconfig.html">run +configuration</a> that includes the <code>--user <var>user_id</var></code> flag, +specifying the Work profile user ID. You can find the user ID by executing +<code>adb shell pm list users</code> from command line. For more information, +see the <a href="/studio/run/index.html#ir-work-profile">Instant Run +documentation</a>.</p> + + <h3>Provision a device owner</h3> <p> diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 5c54324d8107..c3861083e2fd 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -77,6 +77,8 @@ public class SurfaceTexture { private long mProducer; private long mFrameAvailableListener; + private boolean mIsSingleBuffered; + /** * Callback interface for being notified that a new stream frame is available. */ @@ -130,6 +132,7 @@ public class SurfaceTexture { */ public SurfaceTexture(int texName, boolean singleBufferMode) { mCreatorLooper = Looper.myLooper(); + mIsSingleBuffered = singleBufferMode; nativeInit(false, texName, singleBufferMode, new WeakReference<SurfaceTexture>(this)); } @@ -157,6 +160,7 @@ public class SurfaceTexture { */ public SurfaceTexture(boolean singleBufferMode) { mCreatorLooper = Looper.myLooper(); + mIsSingleBuffered = singleBufferMode; nativeInit(true, 0, singleBufferMode, new WeakReference<SurfaceTexture>(this)); } @@ -378,6 +382,14 @@ public class SurfaceTexture { } } + /** + * Returns true if the SurfaceTexture is single-buffered + * @hide + */ + public boolean isSingleBuffered() { + return mIsSingleBuffered; + } + private native void nativeInit(boolean isDetached, int texName, boolean singleBufferMode, WeakReference<SurfaceTexture> weakSelf) throws Surface.OutOfResourcesException; diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index c836204486b0..0f305f3cff3d 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -144,6 +144,55 @@ import java.util.ArrayList; * android:valueType="pathType"/> * </set> * </pre></li> + * <p> + * Since AAPT tool is now supporting a new format which can bundle several related XML files into + * one, we can merge the previous example into one XML file, like this: + * </p> + * <pre> + * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" > + * <aapt:attr name="android:drawable"> + * <vector + * android:height="64dp" + * android:width="64dp" + * android:viewportHeight="600" + * android:viewportWidth="600" > + * <group + * android:name="rotationGroup" + * android:pivotX="300.0" + * android:pivotY="300.0" + * android:rotation="45.0" > + * <path + * android:name="v" + * android:fillColor="#000000" + * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> + * </group> + * </vector> + * </aapt:attr> + * + * <target android:name="rotationGroup"> * + * <aapt:attr name="android:animation"> + * <objectAnimator + * android:duration="6000" + * android:propertyName="rotation" + * android:valueFrom="0" + * android:valueTo="360" /> + * </aapt:attr> + * </target> + * + * <target android:name="v" > + * <aapt:attr name="android:animation"> + * <set> + * <objectAnimator + * android:duration="3000" + * android:propertyName="pathData" + * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z" + * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z" + * android:valueType="pathType"/> + * </set> + * </aapt:attr> + * </target> + * </animated-vector> + * </pre> * * @attr ref android.R.styleable#AnimatedVectorDrawable_drawable * @attr ref android.R.styleable#AnimatedVectorDrawableTarget_name diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 7f3a4373639b..3aca867c0347 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1422,9 +1422,10 @@ public abstract class Drawable { /** * Obtains styled attributes from the theme, if available, or unstyled * resources if the theme is null. + * @hide */ - static @NonNull TypedArray obtainAttributes(@NonNull Resources res, @Nullable Theme theme, - @NonNull AttributeSet set, @NonNull int[] attrs) { + protected static @NonNull TypedArray obtainAttributes(@NonNull Resources res, + @Nullable Theme theme, @NonNull AttributeSet set, @NonNull int[] attrs) { if (theme == null) { return res.obtainAttributes(set, attrs); } diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index cc7f5c7cf753..c7a3c75f3545 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -27,8 +27,8 @@ import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; -import android.graphics.Rect; import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.LayoutDirection; @@ -601,8 +601,9 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { * during inflation. * * @param res the resources used to inflate density-dependent values + * @hide */ - final void updateDensity(Resources res) { + protected final void updateDensity(Resources res) { mDrawableContainerState.updateDensity(res); } @@ -711,7 +712,10 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { boolean mHasTintList; boolean mHasTintMode; - DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, + /** + * @hide + */ + protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res) { mOwner = owner; mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null); diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 3dbd2a96b00a..8c633b0da93a 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -475,16 +475,17 @@ public class GradientDrawable extends Drawable { } /** - * Sets the center location in pixels of the gradient. The radius is - * honored only when the gradient type is set to {@link #RADIAL_GRADIENT} - * or {@link #SWEEP_GRADIENT}. + * Sets the position of the center of the gradient as a fraction of the + * width and height. + * <p> + * The default value is (0.5, 0.5). * <p> * <strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property. * - * @param x the x coordinate of the gradient's center in pixels - * @param y the y coordinate of the gradient's center in pixels + * @param x the X-position of the center of the gradient + * @param y the Y-position of the center of the gradient * * @see #mutate() * @see #setGradientType(int) @@ -498,9 +499,10 @@ public class GradientDrawable extends Drawable { } /** - * Returns the center X location of this gradient in pixels. + * Returns the X-position of the center of the gradient as a fraction of + * the width. * - * @return the center X location of this gradient in pixels + * @return the X-position of the center of the gradient * @see #setGradientCenter(float, float) */ public float getGradientCenterX() { @@ -508,9 +510,10 @@ public class GradientDrawable extends Drawable { } /** - * Returns the center Y location of this gradient in pixels. + * Returns the Y-position of the center of this gradient as a fraction of + * the height. * - * @return the center Y location of this gradient in pixels + * @return the Y-position of the center of the gradient * @see #setGradientCenter(float, float) */ public float getGradientCenterY() { @@ -554,19 +557,43 @@ public class GradientDrawable extends Drawable { } /** - * Sets whether or not this drawable will honor its {@code level} property. + * Sets whether this drawable's {@code level} property will be used to + * scale the gradient. If a gradient is not used, this property has no + * effect. * <p> - * <strong>Note</strong>: changing this property will affect all instances + * Scaling behavior varies based on gradient type: + * <ul> + * <li>{@link #LINEAR_GRADIENT} adjusts the ending position along the + * gradient's axis of orientation (see {@link #getOrientation()}) + * <li>{@link #RADIAL_GRADIENT} adjusts the outer radius + * <li>{@link #SWEEP_GRADIENT} adjusts the ending angle + * <ul> + * <p> + * The default value for this property is {@code false}. + * <p> + * <strong>Note</strong>: This property corresponds to the + * {@code android:useLevel} attribute on the inner {@code <gradient>} + * tag, NOT the {@code android:useLevel} attribute on the outer + * {@code <shape>} tag. For example, + * <pre>{@code + * <shape ...> + * <gradient + * ... + * android:useLevel="true" /> + * </shape> + * }</pre><p> + * <strong>Note</strong>: Changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property. * - * @param useLevel {@code true} if this drawable should honor its level, - * {@code false} otherwise + * @param useLevel {@code true} if the gradient should be scaled based on + * level, {@code false} otherwise * * @see #mutate() * @see #setLevel(int) * @see #getLevel() * @see #getUseLevel() + * @attr ref android.R.styleable#GradientDrawableGradient_useLevel */ public void setUseLevel(boolean useLevel) { mGradientState.mUseLevel = useLevel; @@ -575,12 +602,13 @@ public class GradientDrawable extends Drawable { } /** - * Returns whether or not this drawable will honor its {@code level} - * property. + * Returns whether this drawable's {@code level} property will be used to + * scale the gradient. * - * @return {@code true} if this drawable should honor its level, + * @return {@code true} if the gradient should be scaled based on level, * {@code false} otherwise * @see #setUseLevel(boolean) + * @attr ref android.R.styleable#GradientDrawableGradient_useLevel */ public boolean getUseLevel() { return mGradientState.mUseLevel; diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 1864206118b2..c30c4c2f02d6 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -269,7 +269,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { // If the layer doesn't have a drawable or unresolved theme // attribute for a drawable, attempt to parse one from the child - // element. + // element. If multiple child elements exist, we'll only use the + // first one. if (layer.mDrawable == null && (layer.mThemeAttrs == null || layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) { while ((type = parser.next()) == XmlPullParser.TEXT) { @@ -279,13 +280,12 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { + ": <item> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme); - } - if (layer.mDrawable != null) { + // We found a child drawable. Take ownership. + layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme); + layer.mDrawable.setCallback(this); state.mChildrenChangingConfigurations |= layer.mDrawable.getChangingConfigurations(); - layer.mDrawable.setCallback(this); } addLayer(layer); @@ -387,7 +387,19 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable); if (dr != null) { + if (layer.mDrawable != null) { + // It's possible that a drawable was already set, in which case + // we should clear the callback. We may have also integrated the + // drawable's changing configurations, but we don't have enough + // information to revert that change. + layer.mDrawable.setCallback(null); + } + + // Take ownership of the new drawable. layer.mDrawable = dr; + layer.mDrawable.setCallback(this); + state.mChildrenChangingConfigurations |= + layer.mDrawable.getChangingConfigurations(); } } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index dc1d18f3b2bd..9ff69650294d 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -27,9 +27,9 @@ import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.PixelFormat; +import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; -import android.graphics.PorterDuff.Mode; import android.graphics.Shader; import android.util.ArrayMap; import android.util.AttributeSet; @@ -140,12 +140,16 @@ import dalvik.system.VMRuntime; * in the SVG's path data. This is defined in the viewport space.</dd> * <dt><code>android:fillColor</code></dt> * <dd>Specifies the color used to fill the path. May be a color or, for SDK 24+, a color state list - * or a gradient color. If this property is animated, any value set by the animation will - * override the original value. No path fill is drawn if this property is not specified.</dd> + * or a gradient color (See {@link android.R.styleable#GradientColor} + * and {@link android.R.styleable#GradientColorItem}). + * If this property is animated, any value set by the animation will override the original value. + * No path fill is drawn if this property is not specified.</dd> * <dt><code>android:strokeColor</code></dt> * <dd>Specifies the color used to draw the path outline. May be a color or, for SDK 24+, a color - * state list or a gradient color. If this property is animated, any value set by the animation will - * override the original value. No path outline is drawn if this property is not specified.</dd> + * state list or a gradient color (See {@link android.R.styleable#GradientColor} + * and {@link android.R.styleable#GradientColorItem}). + * If this property is animated, any value set by the animation will override the original value. + * No path outline is drawn if this property is not specified.</dd> * <dt><code>android:strokeWidth</code></dt> * <dd>The width a path stroke.</dd> * <dt><code>android:strokeAlpha</code></dt> @@ -166,8 +170,9 @@ import dalvik.system.VMRuntime; * <dt><code>android:strokeMiterLimit</code></dt> * <dd>Sets the Miter limit for a stroked path.</dd> * <dt><code>android:fillType</code></dt> - * <dd>Sets the fillType for a path. It is the same as SVG's "fill-rule" properties. - * For more details, see https://www.w3.org/TR/SVG/painting.html#FillRuleProperty</dd> + * <dd>Sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the + * same as SVG's "fill-rule" properties. For more details, see + * <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd> * </dl></dd> * </dl> * @@ -201,7 +206,26 @@ import dalvik.system.VMRuntime; * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" /> * </group> * </vector> - * </pre></li> + * </pre> + * </li> + * <li>And here is an example of linear gradient color, which is supported in SDK 24+. + * See more details in {@link android.R.styleable#GradientColor} and + * {@link android.R.styleable#GradientColorItem}. + * <pre> + * <gradient xmlns:android="http://schemas.android.com/apk/res/android" + * android:angle="90" + * android:startColor="?android:attr/colorPrimary" + * android:endColor="?android:attr/colorControlActivated" + * android:centerColor="#f00" + * android:startX="0" + * android:startY="0" + * android:endX="100" + * android:endY="100" + * android:type="linear"> + * </gradient> + * </pre> + * </li> + * */ public class VectorDrawable extends Drawable { diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java index 2b70b6a45f82..cd1f8de6ee0f 100644 --- a/graphics/java/android/graphics/pdf/PdfEditor.java +++ b/graphics/java/android/graphics/pdf/PdfEditor.java @@ -79,8 +79,12 @@ public final class PdfEditor { } mInput = input; - mNativeDocument = nativeOpen(mInput.getFd(), size); - mPageCount = nativeGetPageCount(mNativeDocument); + + synchronized (PdfRenderer.sPdfiumLock) { + mNativeDocument = nativeOpen(mInput.getFd(), size); + mPageCount = nativeGetPageCount(mNativeDocument); + } + mCloseGuard.open("close"); } @@ -102,7 +106,10 @@ public final class PdfEditor { public void removePage(int pageIndex) { throwIfClosed(); throwIfPageNotInDocument(pageIndex); - mPageCount = nativeRemovePage(mNativeDocument, pageIndex); + + synchronized (PdfRenderer.sPdfiumLock) { + mPageCount = nativeRemovePage(mNativeDocument, pageIndex); + } } /** @@ -125,11 +132,16 @@ public final class PdfEditor { if (clip == null) { Point size = new Point(); getPageSize(pageIndex, size); - nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance, - 0, 0, size.x, size.y); + + synchronized (PdfRenderer.sPdfiumLock) { + nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance, + 0, 0, size.x, size.y); + } } else { - nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance, - clip.left, clip.top, clip.right, clip.bottom); + synchronized (PdfRenderer.sPdfiumLock) { + nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance, + clip.left, clip.top, clip.right, clip.bottom); + } } } @@ -143,7 +155,10 @@ public final class PdfEditor { throwIfClosed(); throwIfOutSizeNull(outSize); throwIfPageNotInDocument(pageIndex); - nativeGetPageSize(mNativeDocument, pageIndex, outSize); + + synchronized (PdfRenderer.sPdfiumLock) { + nativeGetPageSize(mNativeDocument, pageIndex, outSize); + } } /** @@ -156,7 +171,10 @@ public final class PdfEditor { throwIfClosed(); throwIfOutMediaBoxNull(outMediaBox); throwIfPageNotInDocument(pageIndex); - return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox); + + synchronized (PdfRenderer.sPdfiumLock) { + return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox); + } } /** @@ -169,7 +187,10 @@ public final class PdfEditor { throwIfClosed(); throwIfMediaBoxNull(mediaBox); throwIfPageNotInDocument(pageIndex); - nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox); + + synchronized (PdfRenderer.sPdfiumLock) { + nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox); + } } /** @@ -182,7 +203,10 @@ public final class PdfEditor { throwIfClosed(); throwIfOutCropBoxNull(outCropBox); throwIfPageNotInDocument(pageIndex); - return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox); + + synchronized (PdfRenderer.sPdfiumLock) { + return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox); + } } /** @@ -195,7 +219,10 @@ public final class PdfEditor { throwIfClosed(); throwIfCropBoxNull(cropBox); throwIfPageNotInDocument(pageIndex); - nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox); + + synchronized (PdfRenderer.sPdfiumLock) { + nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox); + } } /** @@ -205,7 +232,10 @@ public final class PdfEditor { */ public boolean shouldScaleForPrinting() { throwIfClosed(); - return nativeScaleForPrinting(mNativeDocument); + + synchronized (PdfRenderer.sPdfiumLock) { + return nativeScaleForPrinting(mNativeDocument); + } } /** @@ -219,7 +249,10 @@ public final class PdfEditor { public void write(ParcelFileDescriptor output) throws IOException { try { throwIfClosed(); - nativeWrite(mNativeDocument, output.getFd()); + + synchronized (PdfRenderer.sPdfiumLock) { + nativeWrite(mNativeDocument, output.getFd()); + } } finally { IoUtils.closeQuietly(output); } @@ -247,7 +280,9 @@ public final class PdfEditor { } private void doClose() { - nativeClose(mNativeDocument); + synchronized (PdfRenderer.sPdfiumLock) { + nativeClose(mNativeDocument); + } IoUtils.closeQuietly(mInput); mInput = null; mCloseGuard.close(); diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java index 520ebe5f2db8..cfc130990e92 100644 --- a/graphics/java/android/graphics/pdf/PdfRenderer.java +++ b/graphics/java/android/graphics/pdf/PdfRenderer.java @@ -99,6 +99,12 @@ import java.lang.annotation.RetentionPolicy; * @see #close() */ public final class PdfRenderer implements AutoCloseable { + /** + * Any call the native pdfium code has to be single threaded as the library does not support + * parallel use. + */ + final static Object sPdfiumLock = new Object(); + private final CloseGuard mCloseGuard = CloseGuard.get(); private final Point mTempPoint = new Point(); @@ -154,8 +160,12 @@ public final class PdfRenderer implements AutoCloseable { } mInput = input; - mNativeDocument = nativeCreate(mInput.getFd(), size); - mPageCount = nativeGetPageCount(mNativeDocument); + + synchronized (sPdfiumLock) { + mNativeDocument = nativeCreate(mInput.getFd(), size); + mPageCount = nativeGetPageCount(mNativeDocument); + } + mCloseGuard.open("close"); } @@ -189,7 +199,10 @@ public final class PdfRenderer implements AutoCloseable { */ public boolean shouldScaleForPrinting() { throwIfClosed(); - return nativeScaleForPrinting(mNativeDocument); + + synchronized (sPdfiumLock) { + return nativeScaleForPrinting(mNativeDocument); + } } /** @@ -224,7 +237,9 @@ public final class PdfRenderer implements AutoCloseable { if (mCurrentPage != null) { mCurrentPage.close(); } - nativeClose(mNativeDocument); + synchronized (sPdfiumLock) { + nativeClose(mNativeDocument); + } try { mInput.close(); } catch (IOException ioe) { @@ -277,7 +292,9 @@ public final class PdfRenderer implements AutoCloseable { private Page(int index) { Point size = mTempPoint; - mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size); + synchronized (sPdfiumLock) { + mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size); + } mIndex = index; mWidth = size.x; mHeight = size.y; @@ -384,8 +401,10 @@ public final class PdfRenderer implements AutoCloseable { final long transformPtr = (transform != null) ? transform.native_instance : 0; - nativeRenderPage(mNativeDocument, mNativePage, destination, contentLeft, - contentTop, contentRight, contentBottom, transformPtr, renderMode); + synchronized (sPdfiumLock) { + nativeRenderPage(mNativeDocument, mNativePage, destination, contentLeft, + contentTop, contentRight, contentBottom, transformPtr, renderMode); + } } /** @@ -412,7 +431,9 @@ public final class PdfRenderer implements AutoCloseable { } private void doClose() { - nativeClosePage(mNativePage); + synchronized (sPdfiumLock) { + nativeClosePage(mNativePage); + } mNativePage = 0; mCloseGuard.close(); mCurrentPage = null; diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index cce58c2096f3..a96ca3922296 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.annotation.WorkerThread; import android.app.Activity; import android.app.PendingIntent; +import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -356,6 +357,9 @@ public final class KeyChain { * * <p> This method may block while waiting for a connection to another process, and must never * be called from the main thread. + * <p> As {@link Activity} and {@link Service} contexts are short-lived and can be destroyed + * at any time from the main thread, it is safer to rely on a long-lived context such as one + * returned from {@link Context#getApplicationContext()}. * * @param alias The alias of the desired private key, typically returned via * {@link KeyChainAliasCallback#alias}. @@ -368,7 +372,7 @@ public final class KeyChain { if (alias == null) { throw new NullPointerException("alias == null"); } - KeyChainConnection keyChainConnection = bind(context); + KeyChainConnection keyChainConnection = bind(context.getApplicationContext()); try { final IKeyChainService keyChainService = keyChainConnection.getService(); final String keyId = keyChainService.requestPrivateKey(alias); @@ -400,6 +404,9 @@ public final class KeyChain { * * <p> This method may block while waiting for a connection to another process, and must never * be called from the main thread. + * <p> As {@link Activity} and {@link Service} contexts are short-lived and can be destroyed + * at any time from the main thread, it is safer to rely on a long-lived context such as one + * returned from {@link Context#getApplicationContext()}. * * @param alias The alias of the desired certificate chain, typically * returned via {@link KeyChainAliasCallback#alias}. @@ -412,7 +419,7 @@ public final class KeyChain { if (alias == null) { throw new NullPointerException("alias == null"); } - KeyChainConnection keyChainConnection = bind(context); + KeyChainConnection keyChainConnection = bind(context.getApplicationContext()); try { IKeyChainService keyChainService = keyChainConnection.getService(); diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 4848630baacf..56af57aa8e96 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -679,14 +679,14 @@ public class ExifInterface { if (value instanceof long[]) { long[] array = (long[]) value; if (array.length == 1) { - return (double) array[0]; + return array[0]; } throw new NumberFormatException("There are more than one component"); } if (value instanceof int[]) { int[] array = (int[]) value; if (array.length == 1) { - return (double) array[0]; + return array[0]; } throw new NumberFormatException("There are more than one component"); } @@ -1083,6 +1083,7 @@ public class ExifInterface { private int mThumbnailOffset; private int mThumbnailLength; private byte[] mThumbnailBytes; + private boolean mIsSupportedFile; // Pattern to check non zero timestamp private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*"); @@ -1472,9 +1473,11 @@ public class ExifInterface { // Process JPEG input stream getJpegAttributes(in); + mIsSupportedFile = true; } catch (IOException e) { // Ignore exceptions in order to keep the compatibility with the old versions of // ExifInterface. + mIsSupportedFile = false; Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file" + "(ExifInterface supports JPEG and some RAW image formats only) " + "or a corrupted JPEG file to ExifInterface.", e); @@ -1553,9 +1556,9 @@ public class ExifInterface { * and make a single call rather than multiple calls for each attribute. */ public void saveAttributes() throws IOException { - if (mIsRaw) { + if (!mIsSupportedFile || mIsRaw) { throw new UnsupportedOperationException( - "ExifInterface does not support saving attributes on RAW formats."); + "ExifInterface only supports saving attributes on JPEG formats."); } if (mIsInputStream || (mSeekableFileDescriptor == null && mFilename == null)) { throw new UnsupportedOperationException( @@ -2352,7 +2355,7 @@ public class ExifInterface { for (int i = 0; i < EXIF_TAGS.length; ++i) { int sum = 0; for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) { - final ExifAttribute exifAttribute = (ExifAttribute) ((Map.Entry) entry).getValue(); + final ExifAttribute exifAttribute = (ExifAttribute) entry.getValue(); final int size = exifAttribute.size(); if (size > 4) { sum += size; diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java index 883c8c6ed75d..b65598cdad63 100644 --- a/obex/javax/obex/ClientOperation.java +++ b/obex/javax/obex/ClientOperation.java @@ -207,7 +207,6 @@ public final class ClientOperation implements Operation, BaseStream { * object */ public synchronized int getResponseCode() throws IOException { - //avoid dup validateConnection if ((mReplyHeader.responseCode == -1) || (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { validateConnection(); @@ -423,8 +422,9 @@ public final class ClientOperation implements Operation, BaseStream { private void validateConnection() throws IOException { ensureOpen(); - // to sure only one privateInput object exist. - if (mPrivateInput == null) { + // Make sure that a response has been recieved from remote + // before continuing + if (mPrivateInput == null || mReplyHeader.responseCode == -1) { startProcessing(); } } diff --git a/packages/CaptivePortalLogin/res/values-bn-rBD/strings.xml b/packages/CaptivePortalLogin/res/values-bn-rBD/strings.xml index 20173b0dc3bf..24cbfbd6430e 100644 --- a/packages/CaptivePortalLogin/res/values-bn-rBD/strings.xml +++ b/packages/CaptivePortalLogin/res/values-bn-rBD/strings.xml @@ -4,7 +4,7 @@ <string name="app_name" msgid="5934709770924185752">"CaptivePortalLogin"</string> <string name="action_use_network" msgid="6076184727448466030">"যেভাবে আছে সেভাবেই এই নেটওয়ার্ক ব্যবহার করুন"</string> <string name="action_do_not_use_network" msgid="4577366536956516683">"এই নেটওয়ার্ক ব্যবহার করবেন না"</string> - <string name="action_bar_label" msgid="917235635415966620">"নেটওয়ার্কে প্রবেশ করুন করুন"</string> + <string name="action_bar_label" msgid="917235635415966620">"নেটওয়ার্কে প্রবেশ করুন"</string> <string name="ssl_error_warning" msgid="6653188881418638872">"আপনি যে নেটওয়ার্কে যোগ দেওয়ার চেষ্টা করছেন তাতে নিরাপত্তার সমস্যা আছে।"</string> <string name="ssl_error_example" msgid="647898534624078900">"উদাহরণস্বরূপ, লগইন পৃষ্ঠাটি প্রদর্শিত প্রতিষ্ঠানের অন্তর্গত নাও হতে পারে৷"</string> <string name="ssl_error_continue" msgid="6492718244923937110">"যাই হোক না কেন ব্রাউজারের মাধ্যমে অবিরত রাখুন"</string> diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java index 14d4e2d942d9..02a912734239 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Events.java +++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java @@ -53,7 +53,8 @@ public final class Events { */ public static boolean isTouchType(int toolType) { return toolType == MotionEvent.TOOL_TYPE_FINGER - || toolType == MotionEvent.TOOL_TYPE_STYLUS; + || toolType == MotionEvent.TOOL_TYPE_STYLUS + || toolType == MotionEvent.TOOL_TYPE_UNKNOWN; } /** diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java index b82f8dd3d7a3..ae8938de1e3d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java @@ -401,8 +401,13 @@ public class FilesActivity extends BaseActivity { return true; } - // Open the Close drawer if it is closed and we're at the top of a root. - if (size <= 1) { + final Intent intent = getIntent(); + final boolean launchedExternally = intent != null && intent.getData() != null + && mState.action == State.ACTION_BROWSE; + + // Open the Close drawer if it is closed and we're at the top of a root, but only when + // not launched by another app. + if (size <= 1 && !launchedExternally) { mDrawer.setOpen(true); // Remember so we don't just close it again if back is pressed again. mDrawerLastFiddled = System.currentTimeMillis(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java b/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java index d2e9885edf14..b3db037697f3 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java +++ b/packages/DocumentsUI/src/com/android/documentsui/LocalPreferences.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; import android.os.UserHandle; import android.preference.PreferenceManager; @@ -85,6 +86,15 @@ public class LocalPreferences { public @interface PermissionStatus {} /** + * Clears all preferences associated with a given package. + * + * <p>Typically called when a package is removed or when user asked to clear its data. + */ + static void clearPackagePreferences(Context context, String packageName) { + clearScopedAccessPreferences(context, packageName); + } + + /** * Methods below are used to keep track of denied user requests on scoped directory access so * the dialog is not offered when user checked the 'Do not ask again' box * @@ -108,6 +118,23 @@ public class LocalPreferences { getPrefs(context).edit().putInt(key, status).apply(); } + private static void clearScopedAccessPreferences(Context context, String packageName) { + final String keySubstring = "|" + packageName + "|"; + final SharedPreferences prefs = getPrefs(context); + Editor editor = null; + for (final String key : prefs.getAll().keySet()) { + if (key.contains(keySubstring)) { + if (editor == null) { + editor = prefs.edit(); + } + editor.remove(key); + } + } + if (editor != null) { + editor.apply(); + } + } + private static String getScopedAccessDenialsKey(String packageName, String uuid, String directory) { final int userId = UserHandle.myUserId(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java index aef63afcbcd7..fd1183fda13d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java +++ b/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java @@ -23,7 +23,7 @@ import android.content.Intent; import android.net.Uri; /** - * Clean up {@link RecentsProvider} when packages are removed. + * Cleans up {@link RecentsProvider} and {@link LocalPreferences} when packages are removed. */ public class PackageReceiver extends BroadcastReceiver { @Override @@ -31,15 +31,19 @@ public class PackageReceiver extends BroadcastReceiver { final ContentResolver resolver = context.getContentResolver(); final String action = intent.getAction(); + final Uri data = intent.getData(); + final String packageName = data == null ? null : data.getSchemeSpecificPart(); + if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { resolver.call(RecentsProvider.buildRecent(), RecentsProvider.METHOD_PURGE, null, null); - + if (packageName != null) { + LocalPreferences.clearPackagePreferences(context, packageName); + } } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { - final Uri data = intent.getData(); - if (data != null) { - final String packageName = data.getSchemeSpecificPart(); + if (packageName != null) { resolver.call(RecentsProvider.buildRecent(), RecentsProvider.METHOD_PURGE_PACKAGE, packageName, null); + LocalPreferences.clearPackagePreferences(context, packageName); } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index b7c0a9c1be1b..8a6723f6cd34 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -53,10 +53,13 @@ import android.provider.DocumentsContract.Document; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.support.v13.view.DragStartHelper; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager.SpanSizeLookup; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.OnItemTouchListener; +import android.support.v7.widget.RecyclerView.Recycler; import android.support.v7.widget.RecyclerView.RecyclerListener; import android.support.v7.widget.RecyclerView.ViewHolder; import android.text.BidiFormatter; @@ -243,7 +246,40 @@ public class DirectoryFragment extends Fragment mRecView.setAdapter(mAdapter); - mLayout = new GridLayoutManager(getContext(), mColumnCount); + // Switch Access Accessibility API needs an {@link AccessibilityDelegate} to know the proper + // route when user selects an UI element. It usually guesses this if the element has an + // {@link OnClickListener}, but since we do not have one for itemView, we will need to + // manually route it to the right behavior. RecyclerView has its own AccessibilityDelegate, + // and routes it to its LayoutManager; so we must override the LayoutManager's accessibility + // methods to route clicks correctly. + mLayout = new GridLayoutManager(getContext(), mColumnCount) { + @Override + public void onInitializeAccessibilityNodeInfoForItem( + RecyclerView.Recycler recycler, RecyclerView.State state, + View host, AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info); + info.addAction(AccessibilityActionCompat.ACTION_CLICK); + } + + @Override + public boolean performAccessibilityActionForItem( + RecyclerView.Recycler recycler, RecyclerView.State state, View view, + int action, Bundle args) { + // We are only handling click events; route all other to default implementation + if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { + RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(view); + if (vh instanceof DocumentHolder) { + DocumentHolder dh = (DocumentHolder) vh; + if (dh.mEventListener != null) { + dh.mEventListener.onActivate(dh); + return true; + } + } + } + return super.performAccessibilityActionForItem(recycler, state, view, action, + args); + } + }; SpanSizeLookup lookup = mAdapter.createSpanSizeLookup(); if (lookup != null) { mLayout.setSpanSizeLookup(lookup); @@ -717,63 +753,57 @@ public class DirectoryFragment extends Fragment private void openDocuments(final Selection selected) { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN); - new GetDocumentsTask() { - @Override - void onDocumentsReady(List<DocumentInfo> docs) { - // TODO: Implement support in Files activity for opening multiple docs. - BaseActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); - } - }.execute(selected); + // Model must be accessed in UI thread, since underlying cursor is not threadsafe. + List<DocumentInfo> docs = mModel.getDocuments(selected); + // TODO: Implement support in Files activity for opening multiple docs. + BaseActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); } private void shareDocuments(final Selection selected) { Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SHARE); - new GetDocumentsTask() { - @Override - void onDocumentsReady(List<DocumentInfo> docs) { - Intent intent; - - // Filter out directories and virtual files - those can't be shared. - List<DocumentInfo> docsForSend = new ArrayList<>(); - for (DocumentInfo doc: docs) { - if (!doc.isDirectory() && !doc.isVirtualDocument()) { - docsForSend.add(doc); - } - } + // Model must be accessed in UI thread, since underlying cursor is not threadsafe. + List<DocumentInfo> docs = mModel.getDocuments(selected); + Intent intent; - if (docsForSend.size() == 1) { - final DocumentInfo doc = docsForSend.get(0); - - intent = new Intent(Intent.ACTION_SEND); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setType(doc.mimeType); - intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri); - - } else if (docsForSend.size() > 1) { - intent = new Intent(Intent.ACTION_SEND_MULTIPLE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.addCategory(Intent.CATEGORY_DEFAULT); - - final ArrayList<String> mimeTypes = new ArrayList<>(); - final ArrayList<Uri> uris = new ArrayList<>(); - for (DocumentInfo doc : docsForSend) { - mimeTypes.add(doc.mimeType); - uris.add(doc.derivedUri); - } + // Filter out directories and virtual files - those can't be shared. + List<DocumentInfo> docsForSend = new ArrayList<>(); + for (DocumentInfo doc: docs) { + if (!doc.isDirectory() && !doc.isVirtualDocument()) { + docsForSend.add(doc); + } + } - intent.setType(findCommonMimeType(mimeTypes)); - intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + if (docsForSend.size() == 1) { + final DocumentInfo doc = docsForSend.get(0); - } else { - return; - } + intent = new Intent(Intent.ACTION_SEND); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setType(doc.mimeType); + intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri); + + } else if (docsForSend.size() > 1) { + intent = new Intent(Intent.ACTION_SEND_MULTIPLE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); - intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via)); - startActivity(intent); + final ArrayList<String> mimeTypes = new ArrayList<>(); + final ArrayList<Uri> uris = new ArrayList<>(); + for (DocumentInfo doc : docsForSend) { + mimeTypes.add(doc.mimeType); + uris.add(doc.derivedUri); } - }.execute(selected); + + intent.setType(findCommonMimeType(mimeTypes)); + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + + } else { + return; + } + + intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via)); + startActivity(intent); } private String generateDeleteMessage(final List<DocumentInfo> docs) { @@ -819,52 +849,51 @@ public class DirectoryFragment extends Fragment assert(!selected.isEmpty()); final DocumentInfo srcParent = getDisplayState().stack.peek(); - new GetDocumentsTask() { - @Override - void onDocumentsReady(final List<DocumentInfo> docs) { - - TextView message = - (TextView) mInflater.inflate(R.layout.dialog_delete_confirmation, null); - message.setText(generateDeleteMessage(docs)); - - // This "insta-hides" files that are being deleted, because - // the delete operation may be not execute immediately (it - // may be queued up on the FileOperationService.) - // To hide the files locally, we call the hide method on the adapter - // ...which a live object...cannot be parceled. - // For that reason, for now, we implement this dialog NOT - // as a fragment (which can survive rotation and have its own state), - // but as a simple runtime dialog. So rotating a device with an - // active delete dialog...results in that dialog disappearing. - // We can do better, but don't have cycles for it now. - new AlertDialog.Builder(getActivity()) - .setView(message) - .setPositiveButton( - android.R.string.yes, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - // Finish selection mode first which clears selection so we - // don't end up trying to deselect deleted documents. - // This is done here, rather in the onActionItemClicked - // so we can avoid de-selecting items in the case where - // the user cancels the delete. - if (mActionMode != null) { - mActionMode.finish(); - } else { - Log.w(TAG, "Action mode is null before deleting documents."); - } - // Hide the files in the UI...since the operation - // might be queued up on FileOperationService. - // We're walking a line here. - mAdapter.hide(selected.getAll()); - FileOperations.delete( - getActivity(), docs, srcParent, getDisplayState().stack); - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); - } - }.execute(selected); + + // Model must be accessed in UI thread, since underlying cursor is not threadsafe. + List<DocumentInfo> docs = mModel.getDocuments(selected); + + TextView message = + (TextView) mInflater.inflate(R.layout.dialog_delete_confirmation, null); + message.setText(generateDeleteMessage(docs)); + + // This "insta-hides" files that are being deleted, because + // the delete operation may be not execute immediately (it + // may be queued up on the FileOperationService.) + // To hide the files locally, we call the hide method on the adapter + // ...which a live object...cannot be parceled. + // For that reason, for now, we implement this dialog NOT + // as a fragment (which can survive rotation and have its own state), + // but as a simple runtime dialog. So rotating a device with an + // active delete dialog...results in that dialog disappearing. + // We can do better, but don't have cycles for it now. + new AlertDialog.Builder(getActivity()) + .setView(message) + .setPositiveButton( + android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + // Finish selection mode first which clears selection so we + // don't end up trying to deselect deleted documents. + // This is done here, rather in the onActionItemClicked + // so we can avoid de-selecting items in the case where + // the user cancels the delete. + if (mActionMode != null) { + mActionMode.finish(); + } else { + Log.w(TAG, "Action mode is null before deleting documents."); + } + // Hide the files in the UI...since the operation + // might be queued up on FileOperationService. + // We're walking a line here. + mAdapter.hide(selected.getAll()); + FileOperations.delete( + getActivity(), docs, srcParent, getDisplayState().stack); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); } private void transferDocuments(final Selection selected, final @OpType int mode) { @@ -898,25 +927,21 @@ public class DirectoryFragment extends Fragment ? R.string.menu_move : R.string.menu_copy; intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId)); - new GetDocumentsTask() { - @Override - void onDocumentsReady(List<DocumentInfo> docs) { - // TODO: Can this move to Fragment bundle state? - getDisplayState().selectedDocumentsForCopy = docs; - - // Determine if there is a directory in the set of documents - // to be copied? Why? Directory creation isn't supported by some roots - // (like Downloads). This informs DocumentsActivity (the "picker") - // to restrict available roots to just those with support. - intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, hasDirectory(docs)); - intent.putExtra(FileOperationService.EXTRA_OPERATION, mode); - - // This just identifies the type of request...we'll check it - // when we reveive a response. - startActivityForResult(intent, REQUEST_COPY_DESTINATION); - } + // Model must be accessed in UI thread, since underlying cursor is not threadsafe. + List<DocumentInfo> docs = mModel.getDocuments(selected); + // TODO: Can this move to Fragment bundle state? + getDisplayState().selectedDocumentsForCopy = docs; + + // Determine if there is a directory in the set of documents + // to be copied? Why? Directory creation isn't supported by some roots + // (like Downloads). This informs DocumentsActivity (the "picker") + // to restrict available roots to just those with support. + intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, hasDirectory(docs)); + intent.putExtra(FileOperationService.EXTRA_OPERATION, mode); - }.execute(selected); + // This just identifies the type of request...we'll check it + // when we reveive a response. + startActivityForResult(intent, REQUEST_COPY_DESTINATION); } private static boolean hasDirectory(List<DocumentInfo> docs) { @@ -935,12 +960,9 @@ public class DirectoryFragment extends Fragment // Rename option is only available in menu when 1 document selected assert(selected.size() == 1); - new GetDocumentsTask() { - @Override - void onDocumentsReady(List<DocumentInfo> docs) { - RenameDocumentFragment.show(getFragmentManager(), docs.get(0)); - } - }.execute(selected); + // Model must be accessed in UI thread, since underlying cursor is not threadsafe. + List<DocumentInfo> docs = mModel.getDocuments(selected); + RenameDocumentFragment.show(getFragmentManager(), docs.get(0)); } @Override @@ -1099,19 +1121,17 @@ public class DirectoryFragment extends Fragment } } - void copySelectionToClipboard(Selection selection) { - assert(!selection.isEmpty()); - new GetDocumentsTask() { - @Override - void onDocumentsReady(List<DocumentInfo> docs) { - mClipper.clipDocuments(docs); - Activity activity = getActivity(); - Snackbars.makeSnackbar(activity, - activity.getResources().getQuantityString( - R.plurals.clipboard_files_clipped, docs.size(), docs.size()), - Snackbar.LENGTH_SHORT).show(); - } - }.execute(selection); + void copySelectionToClipboard(Selection selected) { + assert(!selected.isEmpty()); + + // Model must be accessed in UI thread, since underlying cursor is not threadsafe. + List<DocumentInfo> docs = mModel.getDocuments(selected); + mClipper.clipDocuments(docs); + Activity activity = getActivity(); + Snackbars.makeSnackbar(activity, + activity.getResources().getQuantityString( + R.plurals.clipboard_files_clipped, docs.size(), docs.size()), + Snackbar.LENGTH_SHORT).show(); } public void pasteFromClipboard() { @@ -1420,25 +1440,6 @@ public class DirectoryFragment extends Fragment mShadowView.draw(canvas); } } - /** - * Abstract task providing support for loading documents *off* - * the main thread. And if it isn't obvious, creating a list - * of documents (especially large lists) can be pretty expensive. - */ - private abstract class GetDocumentsTask - extends AsyncTask<Selection, Void, List<DocumentInfo>> { - @Override - protected final List<DocumentInfo> doInBackground(Selection... selected) { - return mModel.getDocuments(selected[0]); - } - - @Override - protected final void onPostExecute(List<DocumentInfo> docs) { - onDocumentsReady(docs); - } - - abstract void onDocumentsReady(List<DocumentInfo> docs); - } @Override public boolean isSelected(String modelId) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java index 111817132fa1..1de3bbc20496 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java +++ b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java @@ -117,7 +117,9 @@ final class MoveJob extends CopyJob { byteCopyDocument(src, dest); // Remove the source document. - deleteDocument(src, srcParent); + if(!isCanceled()) { + deleteDocument(src, srcParent); + } } @Override diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 78b99274e444..7fe0d2fd074a 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -566,6 +566,7 @@ public class ExternalStorageProvider extends DocumentsProvider { throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + query = query.toLowerCase(); final File parent; synchronized (mRootsLock) { parent = mRoots.get(rootId).path; diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index cce619e67d19..4950af3e14e0 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -873,7 +873,7 @@ class MtpDatabase { } private static int getRootFlags(int[] operationsSupported) { - int rootFlag = Root.FLAG_SUPPORTS_IS_CHILD; + int rootFlag = Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_LOCAL_ONLY; if (MtpDeviceRecord.isWritingSupported(operationsSupported)) { rootFlag |= Root.FLAG_SUPPORTS_CREATE; } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index 404047b8baed..8c13c813552b 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -128,7 +128,7 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.moveToNext(); assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals( - Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, getInt(cursor, Root.COLUMN_FLAGS)); assertEquals(R.drawable.ic_root_mtp, getInt(cursor, Root.COLUMN_ICON)); assertEquals("Device Storage", getString(cursor, Root.COLUMN_TITLE)); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 9ed15c82f336..d19b46083f91 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -210,7 +210,11 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("1", cursor.getString(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); + assertEquals( + Root.FLAG_SUPPORTS_IS_CHILD | + Root.FLAG_SUPPORTS_CREATE | + Root.FLAG_LOCAL_ONLY, + cursor.getInt(1)); assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); assertEquals("Device A Storage A", cursor.getString(3)); assertEquals("1", cursor.getString(4)); @@ -225,7 +229,8 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { cursor.moveToNext(); cursor.moveToNext(); assertEquals("2", cursor.getString(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD, cursor.getInt(1)); + assertEquals( + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_LOCAL_ONLY, cursor.getInt(1)); assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); assertEquals("Device B Storage B", cursor.getString(3)); assertEquals("2", cursor.getString(4)); @@ -271,7 +276,9 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { cursor.moveToNext(); assertEquals("1", cursor.getString(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); + assertEquals( + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, + cursor.getInt(1)); assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); assertEquals("Device A", cursor.getString(3)); assertEquals("1", cursor.getString(4)); @@ -279,7 +286,9 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { cursor.moveToNext(); assertEquals("2", cursor.getString(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); + assertEquals( + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, + cursor.getInt(1)); assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); assertEquals("Device B Storage B", cursor.getString(3)); assertEquals("2", cursor.getString(4)); diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml index 2db6fb06480c..31a776c99e80 100644 --- a/packages/PrintSpooler/res/layout/print_activity.xml +++ b/packages/PrintSpooler/res/layout/print_activity.xml @@ -16,7 +16,6 @@ <com.android.printspooler.widget.PrintContentView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:printspooler="http://schemas.android.com/apk/res/com.android.printspooler" android:id="@+id/options_content" android:layout_width="fill_parent" android:layout_height="fill_parent"> @@ -28,12 +27,14 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingStart="8dip" + android:layout_marginEnd="16dp" android:elevation="@dimen/preview_controls_elevation" android:background="?android:attr/colorPrimary"> <Spinner android:id="@+id/destination_spinner" - android:layout_width="@dimen/preview_destination_spinner_width" + android:layout_width="wrap_content" + android:minWidth="@dimen/preview_destination_spinner_width" android:layout_height="wrap_content" android:layout_marginTop="4dip" android:dropDownWidth="wrap_content" diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml index 103c157b873f..0d8a90abe64b 100644 --- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml +++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml @@ -16,7 +16,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" - android:layout_height="?android:attr/listPreferredItemHeightSmall" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" style="?android:attr/spinnerItemStyle" android:orientation="horizontal" android:gravity="start|center_vertical"> diff --git a/packages/PrintSpooler/res/values-zh-rCN/strings.xml b/packages/PrintSpooler/res/values-zh-rCN/strings.xml index 3debf8e60cdb..d4e796339bc9 100644 --- a/packages/PrintSpooler/res/values-zh-rCN/strings.xml +++ b/packages/PrintSpooler/res/values-zh-rCN/strings.xml @@ -60,7 +60,7 @@ <item quantity="one">找到 <xliff:g id="COUNT_0">%1$s</xliff:g> 台打印机</item> </plurals> <string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> - <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string> - <string name="printer_info_desc" msgid="7181988788991581654">"关于此打印机的更多信息"</string> + <string name="printer_info_desc" msgid="7181988788991581654">"此打印机的详细信息"</string> <string name="could_not_create_file" msgid="3425025039427448443">"无法创建文件"</string> <string name="print_services_disabled_toast" msgid="9089060734685174685">"部分打印服务已停用"</string> <string name="print_searching_for_printers" msgid="6550424555079932867">"正在搜索打印机"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 7d42211be5e5..682af8bd536f 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -863,4 +863,7 @@ <!-- Label for Help and feedback menu item --> <string name="help_feedback_label">Help & feedback</string> + <!-- Content description for drawer menu button [CHAR_LIMIT=30]--> + <string name="content_description_menu_button">Menu</string> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java index b5295da3cfaf..381f903a9701 100644 --- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java @@ -34,6 +34,8 @@ import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.MetricsProto.MetricsEvent; import java.net.URISyntaxException; import java.util.Locale; @@ -112,6 +114,9 @@ public class HelpUtils { helpMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { + MetricsLogger.action(activity, + MetricsEvent.ACTION_SETTING_HELP_AND_FEEDBACK, + intent.getStringExtra(EXTRA_CONTEXT)); try { activity.startActivityForResult(intent, 0); } catch (ActivityNotFoundException exc) { diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java index 6658c14bd1cd..a50b366c26ac 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java @@ -226,6 +226,7 @@ public class SettingsDrawerActivity extends Activity { public void showMenuIcon() { mShowingMenu = true; getActionBar().setHomeAsUpIndicator(R.drawable.ic_menu); + getActionBar().setHomeActionContentDescription(R.string.content_description_menu_button); getActionBar().setDisplayHomeAsUpEnabled(true); } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 284827b57929..aae9cf6de797 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -29,7 +29,6 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.SparseArray; import android.widget.TextView; - import com.android.settingslib.R; public class AccessPointPreference extends Preference { @@ -44,13 +43,14 @@ public class AccessPointPreference extends Preference { private final StateListDrawable mWifiSld; private final int mBadgePadding; private final UserBadgeCache mBadgeCache; - private TextView mTitleView; + private boolean mForSavedNetworks = false; private AccessPoint mAccessPoint; private Drawable mBadge; private int mLevel; private CharSequence mContentDescription; + private int mDefaultIconResId; static final int[] WIFI_CONNECTION_STRENGTH = { R.string.accessibility_wifi_one_bar, @@ -85,6 +85,24 @@ public class AccessPointPreference extends Preference { refresh(); } + public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, + int iconResId, boolean forSavedNetworks) { + super(context); + mBadgeCache = cache; + mAccessPoint = accessPoint; + mForSavedNetworks = forSavedNetworks; + mAccessPoint.setTag(this); + mLevel = -1; + mDefaultIconResId = iconResId; + + mWifiSld = (StateListDrawable) context.getTheme() + .obtainStyledAttributes(wifi_signal_attributes).getDrawable(0); + + // Distance from the end of the title at which this AP's user badge should sit. + mBadgePadding = context.getResources() + .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding); + } + public AccessPoint getAccessPoint() { return mAccessPoint; } @@ -112,7 +130,7 @@ public class AccessPointPreference extends Preference { protected void updateIcon(int level, Context context) { if (level == -1) { - setIcon(null); + safeSetDefaultIcon(); } else { if (getIcon() == null) { // To avoid a drawing race condition, we first set the state (SECURE/NONE) and then @@ -124,16 +142,24 @@ public class AccessPointPreference extends Preference { ? STATE_SECURED : STATE_NONE); Drawable drawable = mWifiSld.getCurrent(); - if (!mForSavedNetworks) { + if (!mForSavedNetworks && drawable != null) { setIcon(drawable); - } else { - setIcon(null); + return; } } + safeSetDefaultIcon(); } } } + private void safeSetDefaultIcon() { + if (mDefaultIconResId != 0) { + setIcon(mDefaultIconResId); + } else { + setIcon(null); + } + } + protected void updateBadge(Context context) { WifiConfiguration config = mAccessPoint.getConfig(); if (config != null) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 0f7fe6fc5124..a43c398f04ad 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -114,7 +114,7 @@ import java.util.regex.Pattern; public class SettingsProvider extends ContentProvider { private static final boolean DEBUG = false; - private static final boolean DROP_DATABASE_ON_MIGRATION = !Build.IS_DEBUGGABLE; + private static final boolean DROP_DATABASE_ON_MIGRATION = true; private static final String LOG_TAG = "SettingsProvider"; @@ -2142,6 +2142,12 @@ public class SettingsProvider extends ContentProvider { // Now upgrade should work fine. onUpgradeLocked(mUserId, oldVersion, newVersion); + + // Make a note what happened, so we don't wonder why data was lost + String reason = "Settings rebuilt! Current version: " + + curVersion + " while expected: " + newVersion; + getGlobalSettingsLocked().insertSettingLocked( + Settings.Global.DATABASE_DOWNGRADE_REASON, reason, "android"); } // Set the global settings version if owner. @@ -2411,7 +2417,7 @@ public class SettingsProvider extends ContentProvider { } if (currentVersion != newVersion) { - Slog.w("SettingsProvider", "warning: upgrading settings database to version " + Slog.wtf("SettingsProvider", "warning: upgrading settings database to version " + newVersion + " left it at " + currentVersion + " instead; this is probably a bug", new Throwable()); if (DEBUG) { diff --git a/packages/Shell/Android.mk b/packages/Shell/Android.mk index 73a044951bd5..81ab2ffc2a23 100644 --- a/packages/Shell/Android.mk +++ b/packages/Shell/Android.mk @@ -12,6 +12,8 @@ LOCAL_PACKAGE_NAME := Shell LOCAL_CERTIFICATE := platform LOCAL_PRIVILEGED_MODULE := true +LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.shell.* + include $(BUILD_PACKAGE) include $(LOCAL_PATH)/tests/Android.mk diff --git a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java index 8b0759942df8..9fd80d3ef616 100644 --- a/packages/Shell/src/com/android/shell/BugreportStorageProvider.java +++ b/packages/Shell/src/com/android/shell/BugreportStorageProvider.java @@ -132,6 +132,7 @@ public class BugreportStorageProvider extends DocumentsProvider { if (!getFileForDocId(documentId).delete()) { throw new FileNotFoundException("Failed to delete: " + documentId); } + getContext().getContentResolver().notifyChange(getNotificationUri(), null); } // This is used by BugreportProgressService so that the notification uri shared by diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index bdb103aed7e7..de2511574e09 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -150,9 +150,6 @@ <!-- DevicePolicyManager get user restrictions --> <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" /> - <!-- Needed for passing extras with intent ACTION_SHOW_ADMIN_SUPPORT_DETAILS --> - <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> - <!-- TV picture-in-picture --> <uses-permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE" /> diff --git a/packages/SystemUI/res/values-bs-rBA-land/strings.xml b/packages/SystemUI/res/values-bs-rBA-land/strings.xml index bdc652af8f1a..56a4ad276d08 100644 --- a/packages/SystemUI/res/values-bs-rBA-land/strings.xml +++ b/packages/SystemUI/res/values-bs-rBA-land/strings.xml @@ -19,5 +19,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="toast_rotation_locked" msgid="7609673011431556092">"Ekran je sada zaključan u pejzažnom prikazu."</string> + <string name="toast_rotation_locked" msgid="7609673011431556092">"Ekran je sada zaključan u vodoravnom prikazu."</string> </resources> diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml index d0d6c506f72d..2aad63c085f5 100644 --- a/packages/SystemUI/res/values-hy-rAM/strings.xml +++ b/packages/SystemUI/res/values-hy-rAM/strings.xml @@ -32,7 +32,7 @@ <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Ծանուցումներ չկան"</string> <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Ընթացիկ"</string> <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Ծանուցումներ"</string> - <string name="battery_low_title" msgid="6456385927409742437">"Մարտկոցը լիցքաթափվում է"</string> + <string name="battery_low_title" msgid="6456385927409742437">"Մարտկոցի լիցքը սպառվում է"</string> <string name="battery_low_percent_format" msgid="2900940511201380775">"Մնաց <xliff:g id="PERCENTAGE">%s</xliff:g>"</string> <string name="battery_low_percent_format_saver_started" msgid="6859235584035338833">"Մնաց <xliff:g id="PERCENTAGE">%s</xliff:g>: Մարտկոցի տնտեսումը միացված է:"</string> <string name="invalid_charger" msgid="4549105996740522523">"USB լիցքավորումը չի աջակցվում:\nՕգտվեք միայն գործող լիցքավորիչից:"</string> diff --git a/packages/SystemUI/res/values-it/strings_car.xml b/packages/SystemUI/res/values-it/strings_car.xml index ae26c9e102bd..19c4e2b02ad0 100644 --- a/packages/SystemUI/res/values-it/strings_car.xml +++ b/packages/SystemUI/res/values-it/strings_car.xml @@ -20,5 +20,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="car_lockscreen_disclaimer_title" msgid="7997539137376896441">"Guida in modo sicuro"</string> - <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"È necessario essere sempre pienamente coscienti delle condizioni di guida e rispettare le leggi vigenti. Le indicazioni stradali potrebbero essere imprecise, incomplete, pericolose, non adatte, vietate o implicare l\'attraversamento di confini. Anche le informazioni sulle attività commerciali potrebbero essere imprecise o incomplete. I dati non vengono forniti in tempo reale e non è possibile garantire la precisione della geolocalizzazione. Non maneggiare il dispositivo mobile e non utilizzare app non progettate per Android Auto durante la guida."</string> + <string name="car_lockscreen_disclaimer_text" msgid="3061224684092952864">"È necessario essere sempre pienamente informati sulle condizioni della strada e rispettare la legislazione vigente. Le indicazioni stradali potrebbero essere imprecise, incomplete, pericolose, inadatte, vietate o richiedere l\'attraversamento di aree amministrative. Anche le informazioni sugli esercizi commerciali potrebbero essere imprecise o incomplete. I dati forniti non sono aggiornati in tempo reale e non è possibile garantire la precisione della geolocalizzazione. Non maneggiare dispositivi mobili e app non destinate ad Android Auto durante la guida."</string> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6c48b25a93da..a670df5496da 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1243,11 +1243,6 @@ <!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] --> <string name="qs_paging" translatable="false">Use the new Quick Settings</string> - <!-- Toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=60]--> - <string name="overview_nav_bar_gesture">Enable split-screen swipe-up gesture</string> - <!-- Description for the toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=NONE]--> - <string name="overview_nav_bar_gesture_desc">Enable gesture to enter split-screen by swiping up from the Overview button</string> - <!-- Category in the System UI Tuner settings, where new/experimental settings are --> <string name="experimental">Experimental</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 1ee13e96ecfe..1e3b8419de99 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -42,7 +42,7 @@ </style> <!-- Theme used for the activity that shows when the system forced an app to be resizable --> - <style name="ForcedResizableTheme" parent="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"> + <style name="ForcedResizableTheme" parent="@android:style/Theme.Translucent.NoTitleBar"> <item name="android:windowBackground">@drawable/forced_resizable_background</item> <item name="android:statusBarColor">@color/transparent</item> <item name="android:windowAnimationStyle">@style/Animation.ForcedResizable</item> diff --git a/packages/SystemUI/res/xml/other_settings.xml b/packages/SystemUI/res/xml/other_settings.xml index 3c872fa98bc7..ce636cdaf4fb 100644 --- a/packages/SystemUI/res/xml/other_settings.xml +++ b/packages/SystemUI/res/xml/other_settings.xml @@ -18,11 +18,6 @@ xmlns:sysui="http://schemas.android.com/apk/res-auto" android:title="@string/other"> - <com.android.systemui.tuner.TunerSwitch - android:key="overview_nav_bar_gesture" - android:title="@string/overview_nav_bar_gesture" - android:summary="@string/overview_nav_bar_gesture_desc" /> - <!-- importance --> <Preference android:key="power_notification_controls" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 84901ee67e71..b393cf7eb9d5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -90,10 +90,10 @@ public class KeyguardService extends Service { } @Override // Binder interface - public void setOccluded(boolean isOccluded) { + public void setOccluded(boolean isOccluded, boolean animate) { Trace.beginSection("KeyguardService.mBinder#setOccluded"); checkPermission(); - mKeyguardViewMediator.setOccluded(isOccluded); + mKeyguardViewMediator.setOccluded(isOccluded, animate); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index de0c77b8a2d6..4449435dde40 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1114,11 +1114,11 @@ public class KeyguardViewMediator extends SystemUI { /** * Notify us when the keyguard is occluded by another window */ - public void setOccluded(boolean isOccluded) { + public void setOccluded(boolean isOccluded, boolean animate) { Trace.beginSection("KeyguardViewMediator#setOccluded"); if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded); mHandler.removeMessages(SET_OCCLUDED); - Message msg = mHandler.obtainMessage(SET_OCCLUDED, (isOccluded ? 1 : 0), 0); + Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0); mHandler.sendMessage(msg); Trace.endSection(); } @@ -1126,7 +1126,7 @@ public class KeyguardViewMediator extends SystemUI { /** * Handles SET_OCCLUDED message sent by setOccluded() */ - private void handleSetOccluded(boolean isOccluded) { + private void handleSetOccluded(boolean isOccluded, boolean animate) { Trace.beginSection("KeyguardViewMediator#handleSetOccluded"); synchronized (KeyguardViewMediator.this) { if (mHiding && isOccluded) { @@ -1137,7 +1137,7 @@ public class KeyguardViewMediator extends SystemUI { if (mOccluded != isOccluded) { mOccluded = isOccluded; - mStatusBarKeyguardViewManager.setOccluded(isOccluded); + mStatusBarKeyguardViewManager.setOccluded(isOccluded, animate); updateActivityLockScreenState(); adjustStatusBarLocked(); } @@ -1468,7 +1468,7 @@ public class KeyguardViewMediator extends SystemUI { break; case SET_OCCLUDED: Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED"); - handleSetOccluded(msg.arg1 != 0); + handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0); Trace.endSection(); break; case KEYGUARD_TIMEOUT: diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl index 940366444e63..9214eef61df5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl +++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl @@ -35,4 +35,5 @@ oneway interface IRecentsNonSystemUserCallbacks { in Rect initialBounds); void onDraggingInRecents(float distanceFromTop); void onDraggingInRecentsEnded(float velocity); + void showCurrentUserToast(int msgResId, int msgLength); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index e117bfeb1364..72074635999c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -18,10 +18,12 @@ package com.android.systemui.recents; import android.app.ActivityManager; import android.app.UiModeManager; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -53,6 +55,7 @@ import com.android.systemui.recents.events.activity.DockedTopTaskEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; +import com.android.systemui.recents.events.component.ShowUserToastEvent; import com.android.systemui.recents.events.ui.RecentsDrawnEvent; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsTaskLoader; @@ -96,7 +99,7 @@ public class Recents extends SystemUI // and does not reside in the home stack. private String mOverrideRecentsPackageName; - private Handler mHandler; + private Handler mHandler = new Handler(); private RecentsImpl mImpl; private int mDraggingInRecentsCurrentUser; @@ -162,6 +165,20 @@ public class Recents extends SystemUI } }; + + private BroadcastReceiver mSystemUserUnlockedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId != UserHandle.USER_NULL) { + mImpl.onUserUnlocked(userId); + } + } + } + }; + + /** * Returns the callbacks interface that non-system users can call. */ @@ -191,7 +208,7 @@ public class Recents extends SystemUI sSystemServicesProxy = SystemServicesProxy.getInstance(mContext); sTaskLoader = new RecentsTaskLoader(mContext); sConfiguration = new RecentsConfiguration(mContext); - mHandler = new Handler(); + UiModeManager uiModeManager = (UiModeManager) mContext. getSystemService(Context.UI_MODE_SERVICE); if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { @@ -221,6 +238,12 @@ public class Recents extends SystemUI // For the system user, initialize an instance of the interface that we can pass to the // secondary user mSystemToUserCallbacks = new RecentsSystemUser(mContext, mImpl); + + // Listen for user-unlocked to kick off preloading recents + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_UNLOCKED); + mContext.registerReceiverAsUser(mSystemUserUnlockedReceiver, UserHandle.SYSTEM, filter, + null /* permission */, null /* scheduler */); } else { // For the secondary user, bind to the primary user's service to get a persistent // interface to register its implementation and to later update its state @@ -455,8 +478,8 @@ public class Recents extends SystemUI mDraggingInRecentsCurrentUser = currentUser; return true; } else { - Toast.makeText(mContext, R.string.recents_incompatible_app_message, - Toast.LENGTH_SHORT).show(); + EventBus.getDefault().send(new ShowUserToastEvent( + R.string.recents_incompatible_app_message, Toast.LENGTH_SHORT)); return false; } } else { @@ -674,6 +697,27 @@ public class Recents extends SystemUI mImpl.onConfigurationChanged(); } + public final void onBusEvent(ShowUserToastEvent event) { + int currentUser = sSystemServicesProxy.getCurrentUser(); + if (sSystemServicesProxy.isSystemUser(currentUser)) { + mImpl.onShowCurrentUserToast(event.msgResId, event.msgLength); + } else { + if (mSystemToUserCallbacks != null) { + IRecentsNonSystemUserCallbacks callbacks = + mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser); + if (callbacks != null) { + try { + callbacks.showCurrentUserToast(event.msgResId, event.msgLength); + } catch (RemoteException e) { + Log.e(TAG, "Callback failed", e); + } + } else { + Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser); + } + } + } + } + /** * Attempts to register with the system user. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 2757fc4f5cc7..a7f271648e13 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -40,6 +40,7 @@ import android.view.LayoutInflater; import android.view.ViewConfiguration; import android.view.WindowManager; +import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.policy.DockedDividerUtils; import com.android.systemui.R; @@ -186,7 +187,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener reloadResources(); } - public void onBootCompleted() { + public void onUserUnlocked(int userId) { // When we start, preload the data associated with the previous recent tasks. // We can use a new plan since the caches will be the same. RecentsTaskLoader loader = Recents.getTaskLoader(); @@ -199,6 +200,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener loader.loadTasks(mContext, plan, launchOpts); } + public void onBootCompleted() { + // Do nothing + } + public void onConfigurationChanged() { reloadResources(); mDummyStackView.reloadOnConfigurationChange(); @@ -392,6 +397,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity)); } + public void onShowCurrentUserToast(int msgResId, int msgLength) { + Toast.makeText(mContext, msgResId, msgLength).show(); + } + /** * Transitions to the next recent task in the stack. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java index 60bf760bfb44..ff9e89e9e00b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java @@ -38,6 +38,7 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub { private static final int MSG_DOCK_TOP_TASK = 7; private static final int MSG_ON_DRAGGING_IN_RECENTS = 8; private static final int MSG_ON_DRAGGING_IN_RECENTS_ENDED = 9; + private static final int MSG_SHOW_USER_TOAST = 10; private RecentsImpl mImpl; @@ -109,6 +110,11 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub { mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_DRAGGING_IN_RECENTS_ENDED, velocity)); } + @Override + public void showCurrentUserToast(int msgResId, int msgLength) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_SHOW_USER_TOAST, msgResId, msgLength)); + } + private final Handler mHandler = new Handler() { @Override @@ -147,6 +153,9 @@ public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub { case MSG_ON_DRAGGING_IN_RECENTS_ENDED: mImpl.onDraggingInRecentsEnded((Float) msg.obj); break; + case MSG_SHOW_USER_TOAST: + mImpl.onShowCurrentUserToast(msg.arg1, msg.arg2); + break; default: super.handleMessage(msg); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ShowUserToastEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ShowUserToastEvent.java new file mode 100644 index 000000000000..e2b39c39b586 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ShowUserToastEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.events.component; + +import com.android.systemui.recents.events.EventBus; + +/** + * This is sent when we want to show a toast for the current user. + */ +public class ShowUserToastEvent extends EventBus.Event { + + public final int msgResId; + public final int msgLength; + + public ShowUserToastEvent(int msgResId, int msgLength) { + this.msgResId = msgResId; + this.msgLength = msgLength; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 702b076d5aec..fce7f9d51ea4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -1258,7 +1258,7 @@ public class TaskStackLayoutAlgorithm { String innerPrefix = prefix + " "; writer.print(prefix); writer.print(TAG); - writer.write(" numStackTasks="); writer.write(mNumStackTasks); + writer.write(" numStackTasks="); writer.print(mNumStackTasks); writer.println(); writer.print(innerPrefix); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java index 5f083d57f9ae..5920f46b12f6 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java @@ -20,12 +20,14 @@ import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.os.Handler; +import android.os.UserHandle; import android.util.ArraySet; import android.widget.Toast; import com.android.systemui.R; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent; +import com.android.systemui.recents.events.component.ShowUserToastEvent; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; import com.android.systemui.stackdivider.events.StartedDragingEvent; @@ -100,9 +102,8 @@ public class ForcedResizableInfoActivityController { } private void activityDismissingDockedStack() { - Toast toast = Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, - Toast.LENGTH_SHORT); - toast.show(); + EventBus.getDefault().send(new ShowUserToastEvent( + R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT)); } private void showPending() { @@ -112,7 +113,7 @@ public class ForcedResizableInfoActivityController { ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchTaskId(mPendingTaskIds.valueAt(i)); options.setTaskOverlay(true); - mContext.startActivity(intent, options.toBundle()); + mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); } mPendingTaskIds.clear(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 02fdd3fc6424..68de16b2af21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -507,7 +507,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { int intrinsicHeight = getIntrinsicHeight(); mIsPinned = pinned; if (intrinsicHeight != getIntrinsicHeight()) { - notifyHeightChanged(false); + notifyHeightChanged(false /* needsAnimation */); } if (pinned) { setIconAnimationRunning(true); @@ -840,8 +840,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } public void resetHeight() { - mMaxExpandHeight = 0; - mHeadsUpHeight = 0; onHeightReset(); requestLayout(); } @@ -1290,7 +1288,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } mHeadsUpHeight = headsUpChild.getHeight(); if (intrinsicBefore != getIntrinsicHeight()) { - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(true /* needsAnimation */); } } @@ -1398,7 +1396,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (isChildInGroup()) { mGroupManager.setGroupExpanded(mStatusBarNotification, true); } - notifyHeightChanged(false); + notifyHeightChanged(false /* needsAnimation */); } public void setChildrenExpanded(boolean expanded, boolean animate) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java index f98b9e586a1c..df4566b28d8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightStatusBarController.java @@ -27,7 +27,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARE /** * Controls how light status bar flag applies to the icons. */ -public class LightStatusBarController { +public class LightStatusBarController implements BatteryController.BatteryStateChangeCallback { private final StatusBarIconController mIconController; private final BatteryController mBatteryController; @@ -37,6 +37,7 @@ public class LightStatusBarController { private int mDockedStackVisibility; private boolean mFullscreenLight; private boolean mDockedLight; + private int mLastStatusBarMode; private final Rect mLastFullscreenBounds = new Rect(); private final Rect mLastDockedBounds = new Rect(); @@ -45,6 +46,7 @@ public class LightStatusBarController { BatteryController batteryController) { mIconController = iconController; mBatteryController = batteryController; + batteryController.addStateChangedCallback(this); } public void setFingerprintUnlockController( @@ -73,6 +75,7 @@ public class LightStatusBarController { } mFullscreenStackVisibility = newFullscreen; mDockedStackVisibility = newDocked; + mLastStatusBarMode = statusBarMode; mLastFullscreenBounds.set(fullscreenStackBounds); mLastDockedBounds.set(dockedStackBounds); } @@ -123,4 +126,16 @@ public class LightStatusBarController { mIconController.setIconsDark(true, animateChange()); } } + + @Override + public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { + + } + + @Override + public void onPowerSaveChanged(boolean isPowerSave) { + onSystemUiVisibilityChanged(mFullscreenStackVisibility, mDockedStackVisibility, + 0 /* mask */, mLastFullscreenBounds, mLastDockedBounds, true /* sbModeChange*/, + mLastStatusBarMode); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index af851014f04d..a6a5742e240c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -92,6 +92,7 @@ public abstract class PanelView extends FrameLayout { * Whether an instant expand request is currently pending and we are just waiting for layout. */ private boolean mInstantExpanding; + private boolean mAnimateAfterExpanding; PanelBar mBar; @@ -656,7 +657,7 @@ public abstract class PanelView extends FrameLayout { vel = 0; } mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); - if (expandBecauseOfFalsing) { + if (vel == 0) { animator.setDuration(350); } } else { @@ -870,6 +871,7 @@ public abstract class PanelView extends FrameLayout { } mInstantExpanding = true; + mAnimateAfterExpanding = animate; mUpdateFlingOnLayout = false; abortAnimations(); cancelPeek(); @@ -894,7 +896,7 @@ public abstract class PanelView extends FrameLayout { if (mStatusBar.getStatusBarWindow().getHeight() != mStatusBar.getStatusBarHeight()) { getViewTreeObserver().removeOnGlobalLayoutListener(this); - if (animate) { + if (mAnimateAfterExpanding) { notifyExpandingStarted(); fling(0, true /* expand */); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 96fb7a8172d0..b1bea0287ddb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -279,6 +279,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, */ private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200; + /** + * Never let the alpha become zero for surfaces that draw with SRC - otherwise the RenderNode + * won't draw anything and uninitialized memory will show through + * if mScrimSrcModeEnabled. Note that 0.001 is rounded down to 0 in + * libhwui. + */ + private static final float SRC_MIN_ALPHA = 0.002f; + static { boolean onlyCoreApps; boolean freeformWindowManagement; @@ -2209,17 +2217,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (mBackdrop.getVisibility() != View.VISIBLE) { mBackdrop.setVisibility(View.VISIBLE); if (allowEnterAnimation) { - mBackdrop.animate().alpha(1f).withEndAction(new Runnable() { - @Override - public void run() { - mStatusBarWindowManager.setBackdropShowing(true); - } - }); + mBackdrop.setAlpha(SRC_MIN_ALPHA); + mBackdrop.animate().alpha(1f); } else { mBackdrop.animate().cancel(); mBackdrop.setAlpha(1f); - mStatusBarWindowManager.setBackdropShowing(true); } + mStatusBarWindowManager.setBackdropShowing(true); metaDataChanged = true; if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork"); @@ -2282,11 +2286,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } else { mStatusBarWindowManager.setBackdropShowing(false); mBackdrop.animate() - // Never let the alpha become zero - otherwise the RenderNode - // won't draw anything and uninitialized memory will show through - // if mScrimSrcModeEnabled. Note that 0.001 is rounded down to 0 in - // libhwui. - .alpha(0.002f) + .alpha(SRC_MIN_ALPHA) .setInterpolator(Interpolators.ACCELERATE_DECELERATE) .setDuration(300) .setStartDelay(0) @@ -2301,7 +2301,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }); if (mKeyguardFadingAway) { mBackdrop.animate() - // Make it disappear faster, as the focus should be on the activity // behind. .setDuration(mKeyguardFadingAwayDuration / 2) @@ -4120,6 +4119,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } /** + * Plays the animation when an activity that was occluding Keyguard goes away. + */ + public void animateKeyguardUnoccluding() { + mScrimController.animateKeyguardUnoccluding(500); + mNotificationPanel.setExpandedFraction(0f); + animateExpandNotificationsPanel(); + } + + /** * Starts the timeout when we try to start the affordances on Keyguard. We usually rely that * Keyguard goes away via fadeKeyguardAfterLaunchTransition, however, that might not happen * because the launched app crashed or something else went wrong. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 8b87a7fa1596..73a95c201bce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -196,6 +196,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } } + public void animateKeyguardUnoccluding(long duration) { + mAnimateChange = false; + setScrimBehindColor(0f); + mAnimateChange = true; + scheduleUpdate(); + mDurationOverride = duration; + } + public void animateGoingToFullShade(long delay, long duration) { mDurationOverride = duration; mAnimationDelay = delay; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index c72f994cf5a8..def4bc3c5790 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -242,7 +242,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return mStatusBarWindowManager.isShowingWallpaper(); } - public void setOccluded(boolean occluded) { + public void setOccluded(boolean occluded, boolean animate) { if (occluded && !mOccluded && mShowing) { if (mPhoneStatusBar.isInLaunchTransition()) { mOccluded = true; @@ -258,9 +258,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } mOccluded = occluded; - mPhoneStatusBar.updateMediaMetaData(false, false); + mPhoneStatusBar.updateMediaMetaData(false, animate && !occluded); mStatusBarWindowManager.setKeyguardOccluded(occluded); reset(); + if (animate && !occluded) { + mPhoneStatusBar.animateKeyguardUnoccluding(); + } } public boolean isOccluded() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 61bac2d8d3c4..e6066aaa4c31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -59,6 +59,7 @@ public class KeyButtonView extends ImageView implements ButtonDispatcher.ButtonI private AudioManager mAudioManager; private boolean mGestureAborted; private boolean mLongClicked; + private OnClickListener mOnClickListener; private final Runnable mCheckLongPress = new Runnable() { public void run() { @@ -109,6 +110,12 @@ public class KeyButtonView extends ImageView implements ButtonDispatcher.ButtonI mCode = code; } + @Override + public void setOnClickListener(OnClickListener onClickListener) { + super.setOnClickListener(onClickListener); + mOnClickListener = onClickListener; + } + public void loadAsync(String uri) { new AsyncTask<String, Void, Drawable>() { @Override @@ -190,6 +197,7 @@ public class KeyButtonView extends ImageView implements ButtonDispatcher.ButtonI // Provide the same haptic feedback that the system offers for virtual keys. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); } + playSoundEffect(SoundEffectConstants.CLICK); removeCallbacks(mCheckLongPress); postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); break; @@ -215,14 +223,14 @@ public class KeyButtonView extends ImageView implements ButtonDispatcher.ButtonI if (doIt) { sendEvent(KeyEvent.ACTION_UP, 0); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); - playSoundEffect(SoundEffectConstants.CLICK); } else { sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); } } else { // no key code, just a regular ImageView - if (doIt) { - performClick(); + if (doIt && mOnClickListener != null) { + mOnClickListener.onClick(this); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); } } removeCallbacks(mCheckLongPress); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index c8c43101c90f..d1de38c7b9e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -216,7 +216,6 @@ public class NotificationStackScrollLayout extends ViewGroup private float mTopPaddingOverflow; private boolean mDontReportNextOverScroll; private boolean mDontClampNextScroll; - private boolean mRequestViewResizeAnimationOnLayout; private boolean mNeedViewResizeAnimation; private View mExpandedGroupView; private boolean mEverythingNeedsAnimation; @@ -518,10 +517,6 @@ public class NotificationStackScrollLayout extends ViewGroup setMaxLayoutHeight(getHeight()); updateContentHeight(); clampScrollPosition(); - if (mRequestViewResizeAnimationOnLayout) { - requestAnimationOnViewResize(null); - mRequestViewResizeAnimationOnLayout = false; - } requestChildrenUpdate(); updateFirstAndLastBackgroundViews(); } @@ -1966,9 +1961,9 @@ public class NotificationStackScrollLayout extends ViewGroup } private void applyCurrentBackgroundBounds() { - if (!mFadingOut) { - mScrimController.setExcludedBackgroundArea(mCurrentBounds); - } + mScrimController.setExcludedBackgroundArea( + mFadingOut || mParentFadingOut || mAmbientState.isDark() ? null + : mCurrentBounds); invalidate(); } @@ -3104,9 +3099,6 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void onReset(ExpandableView view) { - if (mIsExpanded && mAnimationsEnabled) { - mRequestViewResizeAnimationOnLayout = true; - } updateAnimationState(view); updateChronometerForChild(view); } @@ -3847,11 +3839,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateFadingState() { - if (mFadingOut || mParentFadingOut || mAmbientState.isDark()) { - mScrimController.setExcludedBackgroundArea(null); - } else { - applyCurrentBackgroundBounds(); - } + applyCurrentBackgroundBounds(); updateSrcDrawing(); } diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index c9c58050f367..188f8bf9b7f6 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -58,6 +58,9 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ # UI it doesn't own. This is necessary to allow screenshots to be taken LOCAL_CERTIFICATE := platform +LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.* +LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := com.android.systemui.tests.* + include frameworks/base/packages/SettingsLib/common.mk include $(BUILD_PACKAGE) diff --git a/packages/VpnDialogs/res/values-ro/strings.xml b/packages/VpnDialogs/res/values-ro/strings.xml index 4865e964fc74..e2e1e44021b0 100644 --- a/packages/VpnDialogs/res/values-ro/strings.xml +++ b/packages/VpnDialogs/res/values-ro/strings.xml @@ -25,5 +25,5 @@ <string name="duration" msgid="3584782459928719435">"Durată:"</string> <string name="data_transmitted" msgid="7988167672982199061">"Trimise:"</string> <string name="data_received" msgid="4062776929376067820">"Primite:"</string> - <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> (de) octeți/<xliff:g id="NUMBER_1">%2$s</xliff:g> (de) pachete"</string> + <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> octeți/<xliff:g id="NUMBER_1">%2$s</xliff:g> pachete"</string> </resources> diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 5099db775d60..8a0dfe51189f 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -2207,7 +2207,12 @@ message MetricsEvent { // CATEGORY: SETTINGS ACTION_AMBIENT_DISPLAY = 495; + // ACTION: Settings -> [sub settings activity] -> Options menu -> Help & Support + // SUBTYPE: sub settings classname + ACTION_SETTING_HELP_AND_FEEDBACK = 496; + // ---- End N-MR1 Constants, all N-MR1 constants go above this line ---- + // Add new aosp constants above this line. // END OF AOSP CONSTANTS } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 695ea606a90b..8dca14f13202 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2208,6 +2208,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { AccessibilityServiceInfo mAccessibilityServiceInfo; + // The service that's bound to this instance. Whenever this value is non-null, this + // object is registered as a death recipient IBinder mService; IAccessibilityServiceClient mServiceInterface; @@ -2342,14 +2344,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } else { userState.mBindingServices.add(mComponentName); - mService = userState.mUiAutomationServiceClient.asBinder(); mMainHandler.post(new Runnable() { @Override public void run() { // Simulate asynchronous connection since in onServiceConnected // we may modify the state data in case of an error but bind is // called while iterating over the data and bad things can happen. - onServiceConnected(mComponentName, mService); + onServiceConnected(mComponentName, + userState.mUiAutomationServiceClient.asBinder()); } }); userState.mUiAutomationService = this; @@ -2441,7 +2443,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { synchronized (mLock) { - mService = service; + if (mService != service) { + if (mService != null) { + mService.unlinkToDeath(this, 0); + } + mService = service; + try { + mService.linkToDeath(this, 0); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Failed registering death link"); + binderDied(); + return; + } + } mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); UserState userState = getUserStateLocked(mUserId); addServiceLocked(this, userState); @@ -3075,7 +3089,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } public void onAdded() throws RemoteException { - linkToOwnDeathLocked(); final long identity = Binder.clearCallingIdentity(); try { mWindowManagerService.addWindowToken(mOverlayWindowToken, @@ -3092,17 +3105,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identity); } - unlinkToOwnDeathLocked(); - } - - public void linkToOwnDeathLocked() throws RemoteException { - mService.linkToDeath(this, 0); - } - - public void unlinkToOwnDeathLocked() { - if (mService != null) { - mService.unlinkToDeath(this, 0); - } } public void resetLocked() { @@ -3115,7 +3117,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } catch (RemoteException re) { /* ignore */ } - mService = null; + if (mService != null) { + mService.unlinkToDeath(this, 0); + mService = null; + } mServiceInterface = null; } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 9d3889b3bdf7..b5fcb5cb6a3b 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -2266,6 +2266,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku pw.print(info.updatePeriodMillis); pw.print(" resizeMode="); pw.print(info.resizeMode); + pw.print(" widgetCategory="); pw.print(info.widgetCategory); pw.print(" autoAdvanceViewId="); pw.print(info.autoAdvanceViewId); diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index b5b0cd86f397..4caeba84b202 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -875,7 +875,7 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_IGNORED; } synchronized (this) { - if (isOpRestricted(uid, code, resolvedPackageName)) { + if (isOpRestrictedLocked(uid, code, resolvedPackageName)) { return AppOpsManager.MODE_IGNORED; } code = AppOpsManager.opToSwitch(code); @@ -1024,7 +1024,7 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_ERRORED; } Op op = getOpLocked(ops, code, true); - if (isOpRestricted(uid, code, packageName)) { + if (isOpRestrictedLocked(uid, code, packageName)) { return AppOpsManager.MODE_IGNORED; } if (op.duration == -1) { @@ -1082,7 +1082,7 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_ERRORED; } Op op = getOpLocked(ops, code, true); - if (isOpRestricted(uid, code, resolvedPackageName)) { + if (isOpRestrictedLocked(uid, code, resolvedPackageName)) { return AppOpsManager.MODE_IGNORED; } final int switchCode = AppOpsManager.opToSwitch(code); @@ -1308,7 +1308,7 @@ public class AppOpsService extends IAppOpsService.Stub { return op; } - private boolean isOpRestricted(int uid, int code, String packageName) { + private boolean isOpRestrictedLocked(int uid, int code, String packageName) { int userHandle = UserHandle.getUserId(uid); final int restrictionSetCount = mOpUserRestrictions.size(); @@ -2210,24 +2210,32 @@ public class AppOpsService extends IAppOpsService.Stub { private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token, int userHandle, String[] exceptionPackages) { - ClientRestrictionState restrictionState = mOpUserRestrictions.get(token); + boolean notifyChange = false; - if (restrictionState == null) { - try { - restrictionState = new ClientRestrictionState(token); - } catch (RemoteException e) { - return; + synchronized (AppOpsService.this) { + ClientRestrictionState restrictionState = mOpUserRestrictions.get(token); + + if (restrictionState == null) { + try { + restrictionState = new ClientRestrictionState(token); + } catch (RemoteException e) { + return; + } + mOpUserRestrictions.put(token, restrictionState); } - mOpUserRestrictions.put(token, restrictionState); - } - if (restrictionState.setRestriction(code, restricted, exceptionPackages, userHandle)) { - notifyWatchersOfChange(code); + if (restrictionState.setRestriction(code, restricted, exceptionPackages, userHandle)) { + notifyChange = true; + } + + if (restrictionState.isDefault()) { + mOpUserRestrictions.remove(token); + restrictionState.destroy(); + } } - if (restrictionState.isDefault()) { - mOpUserRestrictions.remove(token); - restrictionState.destroy(); + if (notifyChange) { + notifyWatchersOfChange(code); } } @@ -2263,10 +2271,12 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void removeUser(int userHandle) throws RemoteException { checkSystemUid("removeUser"); - final int tokenCount = mOpUserRestrictions.size(); - for (int i = tokenCount - 1; i >= 0; i--) { - ClientRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i); - opRestrictions.removeUser(userHandle); + synchronized (AppOpsService.this) { + final int tokenCount = mOpUserRestrictions.size(); + for (int i = tokenCount - 1; i >= 0; i--) { + ClientRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i); + opRestrictions.removeUser(userHandle); + } } } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index c9bbb7609f13..2f96b20e512a 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -22,6 +22,7 @@ import android.accounts.Account; import android.accounts.AccountAndUser; import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; +import android.accounts.AccountManagerInternal; import android.accounts.AuthenticatorDescription; import android.accounts.CantAddAccountActivity; import android.accounts.GrantCredentialsPermissionActivity; @@ -29,11 +30,14 @@ import android.accounts.IAccountAuthenticator; import android.accounts.IAccountAuthenticatorResponse; import android.accounts.IAccountManager; import android.accounts.IAccountManagerResponse; +import android.annotation.IntRange; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerNative; +import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -46,9 +50,11 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -72,11 +78,14 @@ import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; +import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -86,6 +95,7 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.content.PackageMonitor; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -237,6 +247,13 @@ public class AccountManagerService + " AND " + ACCOUNTS_NAME + "=?" + " AND " + ACCOUNTS_TYPE + "=?"; + private static final String COUNT_OF_MATCHING_GRANTS_ANY_TOKEN = "" + + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS + + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID + + " AND " + GRANTS_GRANTEE_UID + "=?" + + " AND " + ACCOUNTS_NAME + "=?" + + " AND " + ACCOUNTS_TYPE + "=?"; + private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT = AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; @@ -376,6 +393,118 @@ public class AccountManagerService } } }, UserHandle.ALL, userFilter, null, null); + + LocalServices.addService(AccountManagerInternal.class, new AccountManagerInternalImpl()); + + // Need to cancel account request notifications if the update/install can access the account + new PackageMonitor() { + @Override + public void onPackageAdded(String packageName, int uid) { + // Called on a handler, and running as the system + cancelAccountAccessRequestNotificationIfNeeded(uid, true); + } + + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + // Called on a handler, and running as the system + cancelAccountAccessRequestNotificationIfNeeded(uid, true); + } + }.register(mContext, mMessageHandler.getLooper(), UserHandle.ALL, true); + + // Cancel account request notification if an app op was preventing the account access + mAppOpsManager.startWatchingMode(AppOpsManager.OP_GET_ACCOUNTS, null, + new AppOpsManager.OnOpChangedInternalListener() { + @Override + public void onOpChanged(int op, String packageName) { + try { + final int userId = ActivityManager.getCurrentUser(); + final int uid = mPackageManager.getPackageUidAsUser(packageName, userId); + final int mode = mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_GET_ACCOUNTS, uid, packageName); + if (mode == AppOpsManager.MODE_ALLOWED) { + final long identity = Binder.clearCallingIdentity(); + try { + cancelAccountAccessRequestNotificationIfNeeded(packageName, uid, true); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } catch (NameNotFoundException e) { + /* ignore */ + } + } + }); + + // Cancel account request notification if a permission was preventing the account access + mPackageManager.addOnPermissionsChangeListener( + (int uid) -> { + Account[] accounts = null; + String[] packageNames = mPackageManager.getPackagesForUid(uid); + if (packageNames != null) { + final int userId = UserHandle.getUserId(uid); + final long identity = Binder.clearCallingIdentity(); + try { + for (String packageName : packageNames) { + if (mContext.getPackageManager().checkPermission( + Manifest.permission.GET_ACCOUNTS, packageName) + != PackageManager.PERMISSION_GRANTED) { + continue; + } + + if (accounts == null) { + accounts = getAccountsAsUser(null, userId, "android"); + if (ArrayUtils.isEmpty(accounts)) { + return; + } + } + + for (Account account : accounts) { + cancelAccountAccessRequestNotificationIfNeeded( + account, uid, packageName, true); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + }); + } + + private void cancelAccountAccessRequestNotificationIfNeeded(int uid, + boolean checkAccess) { + Account[] accounts = getAccountsAsUser(null, UserHandle.getUserId(uid), "android"); + for (Account account : accounts) { + cancelAccountAccessRequestNotificationIfNeeded(account, uid, checkAccess); + } + } + + private void cancelAccountAccessRequestNotificationIfNeeded(String packageName, int uid, + boolean checkAccess) { + Account[] accounts = getAccountsAsUser(null, UserHandle.getUserId(uid), "android"); + for (Account account : accounts) { + cancelAccountAccessRequestNotificationIfNeeded(account, uid, packageName, checkAccess); + } + } + + private void cancelAccountAccessRequestNotificationIfNeeded(Account account, int uid, + boolean checkAccess) { + String[] packageNames = mPackageManager.getPackagesForUid(uid); + if (packageNames != null) { + for (String packageName : packageNames) { + cancelAccountAccessRequestNotificationIfNeeded(account, uid, + packageName, checkAccess); + } + } + } + + private void cancelAccountAccessRequestNotificationIfNeeded(Account account, + int uid, String packageName, boolean checkAccess) { + if (!checkAccess || hasAccountAccess(account, packageName, + UserHandle.getUserHandleForUid(uid))) { + cancelNotification(getCredentialPermissionNotificationId(account, + AccountManager.ACCOUNT_ACCESS_TOKEN, uid), packageName, + UserHandle.getUserHandleForUid(uid)); + } } @Override @@ -1722,6 +1851,21 @@ public class AccountManagerService } finally { Binder.restoreCallingIdentity(id); } + + if (isChanged) { + synchronized (accounts.credentialsPermissionNotificationIds) { + for (Pair<Pair<Account, String>, Integer> key + : accounts.credentialsPermissionNotificationIds.keySet()) { + if (account.equals(key.first.first) + && AccountManager.ACCOUNT_ACCESS_TOKEN.equals(key.first.second)) { + final int uid = (Integer) key.second; + mMessageHandler.post(() -> cancelAccountAccessRequestNotificationIfNeeded( + account, uid, false)); + } + } + } + } + return isChanged; } @@ -2118,7 +2262,7 @@ public class AccountManagerService final int callingUid = getCallingUid(); clearCallingIdentity(); - if (callingUid != Process.SYSTEM_UID) { + if (UserHandle.getAppId(callingUid) != Process.SYSTEM_UID) { throw new SecurityException("can only call from system"); } int userId = UserHandle.getUserId(callingUid); @@ -2318,9 +2462,11 @@ public class AccountManagerService if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) { Intent intent = newGrantCredentialsPermissionIntent( account, + null, callerUid, new AccountAuthenticatorResponse(this), - authTokenType); + authTokenType, + true); Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); onResult(bundle); @@ -2371,7 +2517,7 @@ public class AccountManagerService intent); doNotification(mAccounts, account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE), - intent, accounts.userId); + intent, "android", accounts.userId); } } super.onResult(result); @@ -2402,7 +2548,7 @@ public class AccountManagerService } private void createNoCredentialsPermissionNotification(Account account, Intent intent, - int userId) { + String packageName, int userId) { int uid = intent.getIntExtra( GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1); String authTokenType = intent.getStringExtra( @@ -2430,20 +2576,23 @@ public class AccountManagerService PendingIntent.FLAG_CANCEL_CURRENT, null, user)) .build(); installNotification(getCredentialPermissionNotificationId( - account, authTokenType, uid), n, user); + account, authTokenType, uid), n, packageName, user.getIdentifier()); } - private Intent newGrantCredentialsPermissionIntent(Account account, int uid, - AccountAuthenticatorResponse response, String authTokenType) { + private Intent newGrantCredentialsPermissionIntent(Account account, String packageName, + int uid, AccountAuthenticatorResponse response, String authTokenType, + boolean startInNewTask) { Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class); - // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag. - // Since it was set in Eclair+ we can't change it without breaking apps using - // the intent from a non-Activity context. - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory( - String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid))); + if (startInNewTask) { + // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag. + // Since it was set in Eclair+ we can't change it without breaking apps using + // the intent from a non-Activity context. This is the default behavior. + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + intent.addCategory(String.valueOf(getCredentialPermissionNotificationId(account, + authTokenType, uid) + (packageName != null ? packageName : ""))); intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account); intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType); intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response); @@ -3294,6 +3443,123 @@ public class AccountManagerService } @Override + public boolean hasAccountAccess(@NonNull Account account, @NonNull String packageName, + @NonNull UserHandle userHandle) { + if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { + throw new SecurityException("Can be called only by system UID"); + } + Preconditions.checkNotNull(account, "account cannot be null"); + Preconditions.checkNotNull(packageName, "packageName cannot be null"); + Preconditions.checkNotNull(userHandle, "userHandle cannot be null"); + + final int userId = userHandle.getIdentifier(); + + Preconditions.checkArgumentInRange(userId, 0, Integer.MAX_VALUE, "user must be concrete"); + + try { + + final int uid = mPackageManager.getPackageUidAsUser(packageName, userId); + // Use null token which means any token. Having a token means the package + // is trusted by the authenticator, hence it is fine to access the account. + if (permissionIsGranted(account, null, uid, userId)) { + return true; + } + // In addition to the permissions required to get an auth token we also allow + // the account to be accessed by holders of the get accounts permissions. + return checkUidPermission(Manifest.permission.GET_ACCOUNTS_PRIVILEGED, uid, packageName) + || checkUidPermission(Manifest.permission.GET_ACCOUNTS, uid, packageName); + } catch (NameNotFoundException e) { + return false; + } + } + + private boolean checkUidPermission(String permission, int uid, String opPackageName) { + final long identity = Binder.clearCallingIdentity(); + try { + IPackageManager pm = ActivityThread.getPackageManager(); + if (pm.checkUidPermission(permission, uid) != PackageManager.PERMISSION_GRANTED) { + return false; + } + final int opCode = AppOpsManager.permissionToOpCode(permission); + return (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow( + opCode, uid, opPackageName) == AppOpsManager.MODE_ALLOWED); + } catch (RemoteException e) { + /* ignore - local call */ + } finally { + Binder.restoreCallingIdentity(identity); + } + return false; + } + + @Override + public IntentSender createRequestAccountAccessIntentSenderAsUser(@NonNull Account account, + @NonNull String packageName, @NonNull UserHandle userHandle) { + if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { + throw new SecurityException("Can be called only by system UID"); + } + + Preconditions.checkNotNull(account, "account cannot be null"); + Preconditions.checkNotNull(packageName, "packageName cannot be null"); + Preconditions.checkNotNull(userHandle, "userHandle cannot be null"); + + final int userId = userHandle.getIdentifier(); + + Preconditions.checkArgumentInRange(userId, 0, Integer.MAX_VALUE, "user must be concrete"); + + final int uid; + try { + uid = mPackageManager.getPackageUidAsUser(packageName, userId); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Unknown package " + packageName); + return null; + } + + Intent intent = newRequestAccountAccessIntent(account, packageName, uid, null); + + final long identity = Binder.clearCallingIdentity(); + try { + return PendingIntent.getActivityAsUser( + mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, + null, new UserHandle(userId)).getIntentSender(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private Intent newRequestAccountAccessIntent(Account account, String packageName, + int uid, RemoteCallback callback) { + return newGrantCredentialsPermissionIntent(account, packageName, uid, + new AccountAuthenticatorResponse(new IAccountAuthenticatorResponse.Stub() { + @Override + public void onResult(Bundle value) throws RemoteException { + handleAuthenticatorResponse(true); + } + + @Override + public void onRequestContinued() { + /* ignore */ + } + + @Override + public void onError(int errorCode, String errorMessage) throws RemoteException { + handleAuthenticatorResponse(false); + } + + private void handleAuthenticatorResponse(boolean accessGranted) throws RemoteException { + cancelNotification(getCredentialPermissionNotificationId(account, + AccountManager.ACCOUNT_ACCESS_TOKEN, uid), packageName, + UserHandle.getUserHandleForUid(uid)); + if (callback != null) { + Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, accessGranted); + callback.sendResult(result); + } + } + }), AccountManager.ACCOUNT_ACCESS_TOKEN, false); + } + + @Override public boolean someUserHasAccount(@NonNull final Account account) { if (!UserHandle.isSameApp(Process.SYSTEM_UID, Binder.getCallingUid())) { throw new SecurityException("Only system can check for accounts across users"); @@ -4933,7 +5199,7 @@ public class AccountManagerService } private void doNotification(UserAccounts accounts, Account account, CharSequence message, - Intent intent, int userId) { + Intent intent, String packageName, final int userId) { long identityToken = clearCallingIdentity(); try { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -4943,12 +5209,12 @@ public class AccountManagerService if (intent.getComponent() != null && GrantCredentialsPermissionActivity.class.getName().equals( intent.getComponent().getClassName())) { - createNoCredentialsPermissionNotification(account, intent, userId); + createNoCredentialsPermissionNotification(account, intent, packageName, userId); } else { + Context contextForUser = getContextForUser(new UserHandle(userId)); final Integer notificationId = getSigninRequiredNotificationId(accounts, account); intent.addCategory(String.valueOf(notificationId)); - UserHandle user = new UserHandle(userId); - Context contextForUser = getContextForUser(user); + final String notificationTitleFormat = contextForUser.getText(R.string.notification_title).toString(); Notification n = new Notification.Builder(contextForUser) @@ -4960,9 +5226,9 @@ public class AccountManagerService .setContentText(message) .setContentIntent(PendingIntent.getActivityAsUser( mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, - null, user)) + null, new UserHandle(userId))) .build(); - installNotification(notificationId, n, user); + installNotification(notificationId, n, packageName, userId); } } finally { restoreCallingIdentity(identityToken); @@ -4970,18 +5236,40 @@ public class AccountManagerService } @VisibleForTesting - protected void installNotification(final int notificationId, final Notification n, + protected void installNotification(int notificationId, final Notification notification, UserHandle user) { - ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) - .notifyAsUser(null, notificationId, n, user); + installNotification(notificationId, notification, "android", user.getIdentifier()); + } + + private void installNotification(int notificationId, final Notification notification, + String packageName, int userId) { + final long token = clearCallingIdentity(); + try { + INotificationManager notificationManager = NotificationManager.getService(); + try { + notificationManager.enqueueNotificationWithTag(packageName, packageName, null, + notificationId, notification, new int[1], userId); + } catch (RemoteException e) { + /* ignore - local call */ + } + } finally { + Binder.restoreCallingIdentity(token); + } } @VisibleForTesting protected void cancelNotification(int id, UserHandle user) { + cancelNotification(id, mContext.getPackageName(), user); + } + + protected void cancelNotification(int id, String packageName, UserHandle user) { long identityToken = clearCallingIdentity(); try { - ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) - .cancelAsUser(null, id, user); + INotificationManager service = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + service.cancelNotificationWithTag(packageName, null, id, user.getIdentifier()); + } catch (RemoteException e) { + /* ignore - local call */ } finally { restoreCallingIdentity(identityToken); } @@ -5042,18 +5330,40 @@ public class AccountManagerService private boolean permissionIsGranted( Account account, String authTokenType, int callerUid, int userId) { - final boolean isPrivileged = isPrivileged(callerUid); - final boolean fromAuthenticator = account != null - && isAccountManagedByCaller(account.type, callerUid, userId); - final boolean hasExplicitGrants = account != null - && hasExplicitlyGrantedPermission(account, authTokenType, callerUid); + if (UserHandle.getAppId(callerUid) == Process.SYSTEM_UID) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Access to " + account + " granted calling uid is system"); + } + return true; + } + + if (isPrivileged(callerUid)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Access to " + account + " granted calling uid " + + callerUid + " privileged"); + } + return true; + } + if (account != null && isAccountManagedByCaller(account.type, callerUid, userId)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Access to " + account + " granted calling uid " + + callerUid + " manages the account"); + } + return true; + } + if (account != null && hasExplicitlyGrantedPermission(account, authTokenType, callerUid)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Access to " + account + " granted calling uid " + + callerUid + " user granted access"); + } + return true; + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid " - + callerUid + ", " + account - + ": is authenticator? " + fromAuthenticator - + ", has explicit permission? " + hasExplicitGrants); + Log.v(TAG, "Access to " + account + " not granted for uid " + callerUid); } - return fromAuthenticator || hasExplicitGrants || isPrivileged; + + return false; } private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId, @@ -5137,16 +5447,26 @@ public class AccountManagerService private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType, int callerUid) { - if (callerUid == Process.SYSTEM_UID) { + if (UserHandle.getAppId(callerUid) == Process.SYSTEM_UID) { return true; } - UserAccounts accounts = getUserAccountsForCaller(); + UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callerUid)); synchronized (accounts.cacheLock) { final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); - String[] args = { String.valueOf(callerUid), authTokenType, - account.name, account.type}; - final boolean permissionGranted = - DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0; + + final String query; + final String[] args; + + if (authTokenType != null) { + query = COUNT_OF_MATCHING_GRANTS; + args = new String[] {String.valueOf(callerUid), authTokenType, + account.name, account.type}; + } else { + query = COUNT_OF_MATCHING_GRANTS_ANY_TOKEN; + args = new String[] {String.valueOf(callerUid), account.name, + account.type}; + } + final boolean permissionGranted = DatabaseUtils.longForQuery(db, query, args) != 0; if (!permissionGranted && ActivityManager.isRunningInTestHarness()) { // TODO: Skip this check when running automated tests. Replace this // with a more general solution. @@ -5245,7 +5565,7 @@ public class AccountManagerService throws RemoteException { final int callingUid = getCallingUid(); - if (callingUid != Process.SYSTEM_UID) { + if (UserHandle.getAppId(callingUid) != Process.SYSTEM_UID) { throw new SecurityException(); } @@ -5287,6 +5607,8 @@ public class AccountManagerService } cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), UserHandle.of(accounts.userId)); + + cancelAccountAccessRequestNotificationIfNeeded(account, uid, true); } } @@ -5604,4 +5926,45 @@ public class AccountManagerService } } } + + private final class AccountManagerInternalImpl extends AccountManagerInternal { + @Override + public void requestAccountAccess(@NonNull Account account, @NonNull String packageName, + @IntRange(from = 0) int userId, @NonNull RemoteCallback callback) { + if (account == null) { + Slog.w(TAG, "account cannot be null"); + return; + } + if (packageName == null) { + Slog.w(TAG, "packageName cannot be null"); + return; + } + if (userId < UserHandle.USER_SYSTEM) { + Slog.w(TAG, "user id must be concrete"); + return; + } + if (callback == null) { + Slog.w(TAG, "callback cannot be null"); + return; + } + + if (hasAccountAccess(account, packageName, new UserHandle(userId))) { + Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + callback.sendResult(result); + return; + } + + final int uid; + try { + uid = mPackageManager.getPackageUidAsUser(packageName, userId); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Unknown package " + packageName); + return; + } + + Intent intent = newRequestAccountAccessIntent(account, packageName, uid, callback); + doNotification(mUsers.get(userId), account, null, intent, packageName, userId); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9e601eb7e07c..aedf089c8c1a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1158,6 +1158,7 @@ public final class ActivityManagerService extends ActivityManagerNative * For example, references to the commonly used services. */ HashMap<String, IBinder> mAppBindArgs; + HashMap<String, IBinder> mIsolatedAppBindArgs; /** * Temporary to avoid allocations. Protected by main lock. @@ -2935,18 +2936,24 @@ public final class ActivityManagerService extends ActivityManagerNative * lazily setup to make sure the services are running when they're asked for. */ private HashMap<String, IBinder> getCommonServicesLocked(boolean isolated) { + // Isolated processes won't get this optimization, so that we don't + // violate the rules about which services they have access to. + if (isolated) { + if (mIsolatedAppBindArgs == null) { + mIsolatedAppBindArgs = new HashMap<>(); + mIsolatedAppBindArgs.put("package", ServiceManager.getService("package")); + } + return mIsolatedAppBindArgs; + } + if (mAppBindArgs == null) { mAppBindArgs = new HashMap<>(); - // Isolated processes won't get this optimization, so that we don't - // violate the rules about which services they have access to. - if (!isolated) { - // Setup the application init args - mAppBindArgs.put("package", ServiceManager.getService("package")); - mAppBindArgs.put("window", ServiceManager.getService("window")); - mAppBindArgs.put(Context.ALARM_SERVICE, - ServiceManager.getService(Context.ALARM_SERVICE)); - } + // Setup the application init args + mAppBindArgs.put("package", ServiceManager.getService("package")); + mAppBindArgs.put("window", ServiceManager.getService("window")); + mAppBindArgs.put(Context.ALARM_SERVICE, + ServiceManager.getService(Context.ALARM_SERVICE)); } return mAppBindArgs; } @@ -13813,7 +13820,7 @@ public final class ActivityManagerService extends ActivityManagerNative try { java.lang.Process logcat = new ProcessBuilder( "/system/bin/timeout", "-k", "15s", "10s", - "/system/bin/logcat", "-v", "time", "-b", "events", "-b", "system", + "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system", "-b", "main", "-b", "crash", "-t", String.valueOf(lines)) .redirectErrorStream(true).start(); @@ -17770,6 +17777,7 @@ public final class ActivityManagerService extends ActivityManagerNative || Intent.ACTION_MEDIA_BUTTON.equals(action) || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action) + || Intent.ACTION_MASTER_CLEAR.equals(action) || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action) || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action) || LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action) diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 525244699082..6c612268331e 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -103,6 +103,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.PowerManagerInternal; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -132,6 +133,9 @@ class ActivityStarter { private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; + // TODO b/30204367 remove when the platform fully supports ephemeral applications + private static final boolean USE_DEFAULT_EPHEMERAL_LAUNCHER = false; + private final ActivityManagerService mService; private final ActivityStackSupervisor mSupervisor; private ActivityStartInterceptor mInterceptor; @@ -456,39 +460,13 @@ class ActivityStarter { // starts either the intent we resolved here [on install error] or the ephemeral // app [on install success]. if (rInfo != null && rInfo.ephemeralResolveInfo != null) { - // Create a pending intent to start the intent resolved here. - final IIntentSender failureTarget = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, - new String[]{ resolvedType }, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE, null); - - // Create a pending intent to start the ephemeral application; force it to be - // directed to the ephemeral package. - ephemeralIntent.setPackage(rInfo.ephemeralResolveInfo.getPackageName()); - final IIntentSender ephemeralTarget = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ ephemeralIntent }, - new String[]{ resolvedType }, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE, null); - - int flags = intent.getFlags(); - intent = new Intent(); - intent.setFlags(flags - | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, - rInfo.ephemeralResolveInfo.getPackageName()); - intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureTarget)); - intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(ephemeralTarget)); - + intent = buildEphemeralInstallerIntent(intent, ephemeralIntent, + rInfo.ephemeralResolveInfo.getPackageName(), callingPackage, resolvedType, + userId); resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; - rInfo = rInfo.ephemeralInstaller; aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); } @@ -543,6 +521,60 @@ class ActivityStarter { return err; } + /** + * Builds and returns an intent to launch the ephemeral installer. + */ + private Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent, + String ephemeralPackage, String callingPackage, String resolvedType, int userId) { + final Intent nonEphemeralIntent = new Intent(origIntent); + nonEphemeralIntent.setFlags(nonEphemeralIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL); + // Intent that is launched if the ephemeral package couldn't be installed + // for any reason. + final IIntentSender failureIntentTarget = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + Binder.getCallingUid(), userId, null /*token*/, null /*resultWho*/, 1, + new Intent[]{ nonEphemeralIntent }, new String[]{ resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, null /*bOptions*/); + + final Intent ephemeralIntent; + if (USE_DEFAULT_EPHEMERAL_LAUNCHER) { + // Force the intent to be directed to the ephemeral package + ephemeralIntent = new Intent(origIntent); + ephemeralIntent.setPackage(ephemeralPackage); + } else { + // Success intent goes back to the installer + // TODO; do we need any extras for the installer? + ephemeralIntent = new Intent(launchIntent); + ephemeralIntent.setData(null); + } + + // Intent that is eventually launched if the ephemeral package was + // installed successfully. This will actually be launched by a platform + // broadcast receiver. + final IIntentSender successIntentTarget = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + Binder.getCallingUid(), userId, null /*token*/, null /*resultWho*/, 0, + new Intent[]{ ephemeralIntent }, new String[]{ resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, null /*bOptions*/); + + // Finally build the actual intent to launch the ephemeral installer + int flags = launchIntent.getFlags(); + final Intent intent = new Intent(); + intent.setFlags(flags + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_NO_HISTORY + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, ephemeralPackage); + intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureIntentTarget)); + intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(successIntentTarget)); + // TODO: Remove when the platform has fully implemented ephemeral apps + intent.setData(origIntent.getData()); + return intent; + } + void postStartActivityUncheckedProcessing( ActivityRecord r, int result, int prevFocusedStackId, ActivityRecord sourceRecord, ActivityStack targetStack) { diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 7eff773543c8..576f2b27f111 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -575,6 +575,8 @@ class AppErrors { boolean handleAppCrashLocked(ProcessRecord app, String reason, String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) { long now = SystemClock.uptimeMillis(); + boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; Long crashTime; Long crashTimePersistent; @@ -612,7 +614,9 @@ class AppErrors { // processes run critical code. mService.removeProcessLocked(app, false, false, "crash"); mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); - return false; + if (!showBackground) { + return false; + } } mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); } else { @@ -705,7 +709,7 @@ class AppErrors { } final boolean crashSilenced = mAppsNotReportingCrashes != null && mAppsNotReportingCrashes.contains(proc.info.packageName); - if (mService.canShowErrorDialogs() && !crashSilenced) { + if ((mService.canShowErrorDialogs() || showBackground) && !crashSilenced) { proc.crashDialog = new AppErrorDialog(mContext, mService, data); } else { // The device is asleep, so just pretend that the user @@ -942,7 +946,9 @@ class AppErrors { null, null, 0, null, null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); - if (mService.canShowErrorDialogs()) { + boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; + if (mService.canShowErrorDialogs() || showBackground) { d = new AppNotRespondingDialog(mService, mContext, proc, (ActivityRecord)data.get("activity"), msg.arg1 != 0); diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 2467a9094072..9c0845363860 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -38,6 +38,7 @@ import com.android.server.am.ActivityStackSupervisor.ActivityContainer; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.util.Objects; final class PendingIntentRecord extends IIntentSender.Stub { private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentRecord" : TAG_AM; @@ -102,7 +103,7 @@ final class PendingIntentRecord extends IIntentSender.Stub { if (requestResolvedType != null) { hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode(); } - hash = (ODD_PRIME_NUMBER*hash) + _p.hashCode(); + hash = (ODD_PRIME_NUMBER*hash) + (_p != null ? _p.hashCode() : 0); hash = (ODD_PRIME_NUMBER*hash) + _t; hashCode = hash; //Slog.i(ActivityManagerService.TAG, this + " hashCode=0x" @@ -121,20 +122,14 @@ final class PendingIntentRecord extends IIntentSender.Stub { if (userId != other.userId){ return false; } - if (!packageName.equals(other.packageName)) { + if (!Objects.equals(packageName, other.packageName)) { return false; } if (activity != other.activity) { return false; } - if (who != other.who) { - if (who != null) { - if (!who.equals(other.who)) { - return false; - } - } else if (other.who != null) { - return false; - } + if (!Objects.equals(who, other.who)) { + return false; } if (requestCode != other.requestCode) { return false; @@ -148,14 +143,8 @@ final class PendingIntentRecord extends IIntentSender.Stub { return false; } } - if (requestResolvedType != other.requestResolvedType) { - if (requestResolvedType != null) { - if (!requestResolvedType.equals(other.requestResolvedType)) { - return false; - } - } else if (other.requestResolvedType != null) { - return false; - } + if (!Objects.equals(requestResolvedType, other.requestResolvedType)) { + return false; } if (flags != other.flags) { return false; diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index 48fecd5c4714..43eb251ba23b 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -87,6 +87,8 @@ public class TaskPersister { private final RecentTasks mRecentTasks; private final SparseArray<SparseBooleanArray> mTaskIdsInFile = new SparseArray<>(); private final File mTaskIdsDir; + // To lock file operations in TaskPersister + private final Object mIoLock = new Object(); /** * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes @@ -195,52 +197,52 @@ public class TaskPersister { return mTaskIdsInFile.get(userId).clone(); } final SparseBooleanArray persistedTaskIds = new SparseBooleanArray(); - BufferedReader reader = null; - String line; - try { - reader = new BufferedReader(new FileReader(getUserPersistedTaskIdsFile(userId))); - while ((line = reader.readLine()) != null) { - for (String taskIdString : line.split("\\s+")) { - int id = Integer.parseInt(taskIdString); - persistedTaskIds.put(id, true); + synchronized (mIoLock) { + BufferedReader reader = null; + String line; + try { + reader = new BufferedReader(new FileReader(getUserPersistedTaskIdsFile(userId))); + while ((line = reader.readLine()) != null) { + for (String taskIdString : line.split("\\s+")) { + int id = Integer.parseInt(taskIdString); + persistedTaskIds.put(id, true); + } } + } catch (FileNotFoundException e) { + // File doesn't exist. Ignore. + } catch (Exception e) { + Slog.e(TAG, "Error while reading taskIds file for user " + userId, e); + } finally { + IoUtils.closeQuietly(reader); } - } catch (FileNotFoundException e) { - // File doesn't exist. Ignore. - } catch (Exception e) { - Slog.e(TAG, "Error while reading taskIds file for user " + userId, e); - } finally { - IoUtils.closeQuietly(reader); } mTaskIdsInFile.put(userId, persistedTaskIds); return persistedTaskIds.clone(); } + @VisibleForTesting - void maybeWritePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds, int userId) { + void writePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds, int userId) { if (userId < 0) { return; } - SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId); - if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIds)) { - return; - } final File persistedTaskIdsFile = getUserPersistedTaskIdsFile(userId); - BufferedWriter writer = null; - try { - writer = new BufferedWriter(new FileWriter(persistedTaskIdsFile)); - for (int i = 0; i < taskIds.size(); i++) { - if (taskIds.valueAt(i)) { - writer.write(String.valueOf(taskIds.keyAt(i))); - writer.newLine(); + synchronized (mIoLock) { + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new FileWriter(persistedTaskIdsFile)); + for (int i = 0; i < taskIds.size(); i++) { + if (taskIds.valueAt(i)) { + writer.write(String.valueOf(taskIds.keyAt(i))); + writer.newLine(); + } } + } catch (Exception e) { + Slog.e(TAG, "Error while writing taskIds file for user " + userId, e); + } finally { + IoUtils.closeQuietly(writer); } - } catch (Exception e) { - Slog.e(TAG, "Error while writing taskIds file for user " + userId, e); - } finally { - IoUtils.closeQuietly(writer); } - mTaskIdsInFile.put(userId, taskIds.clone()); } void unloadUserDataFromMemory(int userId) { @@ -543,16 +545,23 @@ public class TaskPersister { } private void writeTaskIdsFiles() { - int candidateUserIds[]; + SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>(); synchronized (mService) { - candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked(); - } - SparseBooleanArray taskIdsToSave; - for (int userId : candidateUserIds) { - synchronized (mService) { - taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId).clone(); + for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) { + SparseBooleanArray taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId); + SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId); + if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) { + continue; + } else { + SparseBooleanArray taskIdsToSaveCopy = taskIdsToSave.clone(); + mTaskIdsInFile.put(userId, taskIdsToSaveCopy); + changedTaskIdsPerUser.put(userId, taskIdsToSaveCopy); + } } - maybeWritePersistedTaskIdsForUser(taskIdsToSave, userId); + } + for (int i = 0; i < changedTaskIdsPerUser.size(); i++) { + writePersistedTaskIdsForUser(changedTaskIdsPerUser.valueAt(i), + changedTaskIdsPerUser.keyAt(i)); } } diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java index 7525f302c141..c2c1a8c5fef7 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java +++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java @@ -25,6 +25,7 @@ import android.net.NetworkState; import android.net.RouteInfo; import android.net.ip.RouterAdvertisementDaemon; import android.net.ip.RouterAdvertisementDaemon.RaParams; +import android.net.util.NetdService; import android.os.INetworkManagementService; import android.os.ServiceSpecificException; import android.os.RemoteException; @@ -193,7 +194,7 @@ class IPv6TetheringInterfaceServices { private void configureLocalDns( HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) { - INetd netd = getNetdServiceOrNull(); + final INetd netd = NetdService.getInstance(); if (netd == null) { if (newDnses != null) newDnses.clear(); Log.e(TAG, "No netd service instance available; not setting local IPv6 addresses"); @@ -265,18 +266,6 @@ class IPv6TetheringInterfaceServices { return localRoutes; } - private INetd getNetdServiceOrNull() { - if (mNMService != null) { - try { - return mNMService.getNetdService(); - } catch (RemoteException ignored) { - // This blocks until netd can be reached, but it can return - // null during a netd crash. - } - } - return null; - } - // Given a prefix like 2001:db8::/64 return 2001:db8::1. private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) { final byte[] dnsBytes = localPrefix.getRawAddress(); diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 01b23939815c..12955f5771d2 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -482,7 +482,6 @@ public final class ContentService extends IContentService.Stub { SyncManager syncManager = getSyncManager(); if (syncManager != null) { syncManager.scheduleSync(account, userId, uId, authority, extras, - 0 /* no delay */, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } finally { @@ -547,11 +546,8 @@ public final class ContentService extends IContentService.Stub { getSyncManager().updateOrAddPeriodicSync(info, runAtTime, flextime, extras); } else { - long beforeRuntimeMillis = (flextime) * 1000; - long runtimeMillis = runAtTime * 1000; syncManager.scheduleSync( request.getAccount(), userId, callerUid, request.getProvider(), extras, - beforeRuntimeMillis, runtimeMillis, false /* onlyThoseWithUnknownSyncableState */); } } finally { @@ -841,7 +837,7 @@ public final class ContentService extends IContentService.Stub { try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - return syncManager.getIsSyncable( + return syncManager.computeSyncable( account, userId, providerName); } } finally { diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 39ddc3a98317..f739fa81d5aa 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -19,6 +19,7 @@ package com.android.server.content; import android.accounts.Account; import android.accounts.AccountAndUser; import android.accounts.AccountManager; +import android.accounts.AccountManagerInternal; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AppGlobals; @@ -64,6 +65,7 @@ import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -79,6 +81,7 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerInternal; import com.google.android.collect.Lists; @@ -133,6 +136,8 @@ import java.util.Set; public class SyncManager { static final String TAG = "SyncManager"; + private static final boolean DEBUG_ACCOUNT_ACCESS = false; + /** Delay a sync due to local changes this long. In milliseconds */ private static final long LOCAL_SYNC_DELAY; @@ -194,6 +199,11 @@ public class SyncManager { private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock"; + + private static final int SYNC_OP_STATE_VALID = 0; + private static final int SYNC_OP_STATE_INVALID = 1; + private static final int SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS = 2; + private Context mContext; private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0]; @@ -310,6 +320,10 @@ public class SyncManager { private final UserManager mUserManager; + private final AccountManager mAccountManager; + + private final AccountManagerInternal mAccountManagerInternal; + private List<UserInfo> getAllUsers() { return mUserManager.getUsers(); } @@ -490,8 +504,6 @@ public class SyncManager { @Override public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) { scheduleSync(info.account, info.userId, reason, info.provider, extras, - 0 /* no flexMillis */, - 0 /* run immediately */, false); } }); @@ -522,8 +534,7 @@ public class SyncManager { if (!removed) { scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_SERVICE_CHANGED, - type.authority, null, 0 /* no delay */, 0 /* no delay */, - false /* onlyThoseWithUnkownSyncableState */); + type.authority, null, false /* onlyThoseWithUnkownSyncableState */); } } }, mSyncHandler); @@ -562,6 +573,9 @@ public class SyncManager { } mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE); + mAccountManagerInternal = LocalServices.getService(AccountManagerInternal.class); + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); @@ -655,7 +669,7 @@ public class SyncManager { return mSyncStorageEngine; } - public int getIsSyncable(Account account, int userId, String providerName) { + private int getIsSyncable(Account account, int userId, String providerName) { int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName); UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId); @@ -666,22 +680,22 @@ public class SyncManager { RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo( SyncAdapterType.newKey(providerName, account.type), userId); - if (syncAdapterInfo == null) return isSyncable; + if (syncAdapterInfo == null) return AuthorityInfo.NOT_SYNCABLE; PackageInfo pInfo = null; try { pInfo = AppGlobals.getPackageManager().getPackageInfo( syncAdapterInfo.componentName.getPackageName(), 0, userId); - if (pInfo == null) return isSyncable; + if (pInfo == null) return AuthorityInfo.NOT_SYNCABLE; } catch (RemoteException re) { // Shouldn't happen. - return isSyncable; + return AuthorityInfo.NOT_SYNCABLE; } if (pInfo.restrictedAccountType != null && pInfo.restrictedAccountType.equals(account.type)) { return isSyncable; } else { - return 0; + return AuthorityInfo.NOT_SYNCABLE; } } @@ -733,13 +747,10 @@ public class SyncManager { * @param extras a Map of SyncAdapter-specific information to control * syncs of a specific provider. Can be null. Is ignored * if the url is null. - * @param beforeRuntimeMillis milliseconds before runtimeMillis that this sync can run. - * @param runtimeMillis maximum milliseconds in the future to wait before performing sync. * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state. */ public void scheduleSync(Account requestedAccount, int userId, int reason, - String requestedAuthority, Bundle extras, long beforeRuntimeMillis, - long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) { + String requestedAuthority, Bundle extras, boolean onlyThoseWithUnkownSyncableState) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); if (extras == null) { extras = new Bundle(); @@ -749,17 +760,27 @@ public class SyncManager { + requestedAuthority); } - AccountAndUser[] accounts; - if (requestedAccount != null && userId != UserHandle.USER_ALL) { - accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) }; + AccountAndUser[] accounts = null; + if (requestedAccount != null) { + if (userId != UserHandle.USER_ALL) { + accounts = new AccountAndUser[]{new AccountAndUser(requestedAccount, userId)}; + } else { + for (AccountAndUser runningAccount : mRunningAccounts) { + if (requestedAccount.equals(runningAccount.account)) { + accounts = ArrayUtils.appendElement(AccountAndUser.class, + accounts, runningAccount); + } + } + } } else { accounts = mRunningAccounts; - if (accounts.length == 0) { - if (isLoggable) { - Slog.v(TAG, "scheduleSync: no accounts configured, dropping"); - } - return; + } + + if (ArrayUtils.isEmpty(accounts)) { + if (isLoggable) { + Slog.v(TAG, "scheduleSync: no accounts configured, dropping"); } + return; } final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); @@ -808,29 +829,41 @@ public class SyncManager { } for (String authority : syncableAuthorities) { - int isSyncable = getIsSyncable(account.account, account.userId, - authority); + int isSyncable = computeSyncable(account.account, account.userId, authority); + if (isSyncable == AuthorityInfo.NOT_SYNCABLE) { continue; } - final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; - syncAdapterInfo = mSyncAdapters.getServiceInfo( - SyncAdapterType.newKey(authority, account.account.type), account.userId); + + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = + mSyncAdapters.getServiceInfo(SyncAdapterType.newKey(authority, + account.account.type), account.userId); if (syncAdapterInfo == null) { continue; } + final int owningUid = syncAdapterInfo.uid; - final String owningPackage = syncAdapterInfo.componentName.getPackageName(); - try { - if (ActivityManagerNative.getDefault().getAppStartMode(owningUid, - owningPackage) == ActivityManager.APP_START_MODE_DISABLED) { - Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":" - + syncAdapterInfo.componentName - + " -- package not allowed to start"); - continue; + + if (isSyncable == AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS) { + if (isLoggable) { + Slog.v(TAG, " Not scheduling sync operation: " + + "isSyncable == SYNCABLE_NO_ACCOUNT_ACCESS"); } - } catch (RemoteException e) { + Bundle finalExtras = new Bundle(extras); + mAccountManagerInternal.requestAccountAccess(account.account, + syncAdapterInfo.componentName.getPackageName(), + UserHandle.getUserId(owningUid), + new RemoteCallback((Bundle result) -> { + if (result != null + && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) { + scheduleSync(account.account, userId, reason, authority, + finalExtras, onlyThoseWithUnkownSyncableState); + } + } + )); + continue; } + final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs(); final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable(); if (isSyncable < 0 && isAlwaysSyncable) { @@ -838,6 +871,7 @@ public class SyncManager { account.account, account.userId, authority, AuthorityInfo.SYNCABLE); isSyncable = AuthorityInfo.SYNCABLE; } + if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { continue; } @@ -863,6 +897,9 @@ public class SyncManager { account.account, authority, account.userId); long delayUntil = mSyncStorageEngine.getDelayUntilTime(info); + + final String owningPackage = syncAdapterInfo.componentName.getPackageName(); + if (isSyncable < 0) { // Initialisation sync. Bundle newExtras = new Bundle(); @@ -887,8 +924,6 @@ public class SyncManager { if (isLoggable) { Slog.v(TAG, "scheduleSync:" + " delay until " + delayUntil - + " run by " + runtimeMillis - + " flexMillis " + beforeRuntimeMillis + ", source " + source + ", account " + account + ", authority " + authority @@ -904,6 +939,56 @@ public class SyncManager { } } + public int computeSyncable(Account account, int userId, String authority) { + final int status = getIsSyncable(account, userId, authority); + if (status == AuthorityInfo.NOT_SYNCABLE) { + return AuthorityInfo.NOT_SYNCABLE; + } + final SyncAdapterType type = SyncAdapterType.newKey(authority, account.type); + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = + mSyncAdapters.getServiceInfo(type, userId); + if (syncAdapterInfo == null) { + return AuthorityInfo.NOT_SYNCABLE; + } + final int owningUid = syncAdapterInfo.uid; + final String owningPackage = syncAdapterInfo.componentName.getPackageName(); + try { + if (ActivityManagerNative.getDefault().getAppStartMode(owningUid, + owningPackage) == ActivityManager.APP_START_MODE_DISABLED) { + Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":" + + syncAdapterInfo.componentName + + " -- package not allowed to start"); + return AuthorityInfo.NOT_SYNCABLE; + } + } catch (RemoteException e) { + /* ignore - local call */ + } + if (!canAccessAccount(account, owningPackage, owningUid)) { + Log.w(TAG, "Access to " + account + " denied for package " + + owningPackage + " in UID " + syncAdapterInfo.uid); + return AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS; + } + + return status; + } + + private boolean canAccessAccount(Account account, String packageName, int uid) { + if (mAccountManager.hasAccountAccess(account, packageName, + UserHandle.getUserHandleForUid(uid))) { + return true; + } + // We relax the account access rule to also include the system apps as + // they are trusted and we want to minimize the cases where the user + // involvement is required to grant access to the synced account. + try { + mContext.getPackageManager().getApplicationInfoAsUser(packageName, + PackageManager.MATCH_SYSTEM_ONLY, UserHandle.getUserId(uid)); + return true; + } catch (NameNotFoundException e) { + return false; + } + } + private void removeSyncsForAuthority(EndPoint info) { verifyJobScheduler(); List<SyncOperation> ops = getAllPendingSyncs(); @@ -960,8 +1045,6 @@ public class SyncManager { final Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); scheduleSync(account, userId, reason, authority, extras, - LOCAL_SYNC_DELAY /* earliest run time */, - 2 * LOCAL_SYNC_DELAY /* latest sync time. */, false /* onlyThoseWithUnkownSyncableState */); } @@ -1421,7 +1504,6 @@ public class SyncManager { mContext.getOpPackageName()); for (Account account : accounts) { scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, - 0 /* no delay */, 0 /* No flexMillis */, true /* onlyThoseWithUnknownSyncableState */); } } @@ -2530,13 +2612,18 @@ public class SyncManager { } } - if (isOperationValid(op)) { - if (!dispatchSyncOperation(op)) { + final int syncOpState = computeSyncOpState(op); + switch (syncOpState) { + case SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS: + case SYNC_OP_STATE_INVALID: { mSyncJobService.callJobFinished(op.jobId, false); - } - } else { + } return; + } + + if (!dispatchSyncOperation(op)) { mSyncJobService.callJobFinished(op.jobId, false); } + setAuthorityPendingState(op.target); } @@ -2596,8 +2683,7 @@ public class SyncManager { if (syncTargets != null) { scheduleSync(syncTargets.account, syncTargets.userId, - SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, null, 0, 0, - true); + SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, null, true); } } @@ -2665,6 +2751,26 @@ public class SyncManager { SyncStorageEngine.SOURCE_PERIODIC, extras, syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID, pollFrequencyMillis, flexMillis); + + final int syncOpState = computeSyncOpState(op); + switch (syncOpState) { + case SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS: { + mAccountManagerInternal.requestAccountAccess(op.target.account, + op.owningPackage, UserHandle.getUserId(op.owningUid), + new RemoteCallback((Bundle result) -> { + if (result != null + && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) { + updateOrAddPeriodicSync(target, pollFrequency, flex, extras); + } + } + )); + } return; + + case SYNC_OP_STATE_INVALID: { + return; + } + } + scheduleSyncOperationH(op); mSyncStorageEngine.reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } @@ -2725,29 +2831,38 @@ public class SyncManager { /** * Determine if a sync is no longer valid and should be dropped. */ - private boolean isOperationValid(SyncOperation op) { + private int computeSyncOpState(SyncOperation op) { final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); int state; final EndPoint target = op.target; - boolean syncEnabled = mSyncStorageEngine.getMasterSyncAutomatically(target.userId); + // Drop the sync if the account of this operation no longer exists. AccountAndUser[] accounts = mRunningAccounts; if (!containsAccountAndUser(accounts, target.account, target.userId)) { if (isLoggable) { Slog.v(TAG, " Dropping sync operation: account doesn't exist."); } - return false; + return SYNC_OP_STATE_INVALID; } // Drop this sync request if it isn't syncable. - state = getIsSyncable(target.account, target.userId, target.provider); - if (state == 0) { + state = computeSyncable(target.account, target.userId, target.provider); + if (state == AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS) { if (isLoggable) { - Slog.v(TAG, " Dropping sync operation: isSyncable == 0."); + Slog.v(TAG, " Dropping sync operation: " + + "isSyncable == SYNCABLE_NO_ACCOUNT_ACCESS"); } - return false; + return SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS; } - syncEnabled = syncEnabled && mSyncStorageEngine.getSyncAutomatically( - target.account, target.userId, target.provider); + if (state == AuthorityInfo.NOT_SYNCABLE) { + if (isLoggable) { + Slog.v(TAG, " Dropping sync operation: isSyncable == NOT_SYNCABLE"); + } + return SYNC_OP_STATE_INVALID; + } + + final boolean syncEnabled = mSyncStorageEngine.getMasterSyncAutomatically(target.userId) + && mSyncStorageEngine.getSyncAutomatically(target.account, + target.userId, target.provider); // We ignore system settings that specify the sync is invalid if: // 1) It's manual - we try it anyway. When/if it fails it will be rescheduled. @@ -2760,9 +2875,9 @@ public class SyncManager { if (isLoggable) { Slog.v(TAG, " Dropping sync operation: disallowed by settings/network."); } - return false; + return SYNC_OP_STATE_INVALID; } - return true; + return SYNC_OP_STATE_VALID; } private boolean dispatchSyncOperation(SyncOperation op) { diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index bc3fc6a47aef..64849aa321da 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -234,6 +234,12 @@ public class SyncStorageEngine extends Handler { */ public static final int SYNCABLE_NOT_INITIALIZED = 2; + /** + * The adapter is syncable but does not have access to the synced account and needs a + * user access approval. + */ + public static final int SYNCABLE_NO_ACCOUNT_ACCESS = 3; + final EndPoint target; final int ident; boolean enabled; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 0abd2e79b45f..45a96441e57e 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1402,6 +1402,9 @@ public final class DisplayManagerService extends SystemService { throw new IllegalArgumentException("width, height, and densityDpi must be " + "greater than 0"); } + if (surface.isSingleBuffered()) { + throw new IllegalArgumentException("Surface can't be single-buffered"); + } if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) { flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java index 07fa2ce01b3b..e7fd3d88520e 100644 --- a/services/core/java/com/android/server/display/NightDisplayService.java +++ b/services/core/java/com/android/server/display/NightDisplayService.java @@ -182,6 +182,8 @@ public final class NightDisplayService extends SystemService } private void setUp() { + Slog.d(TAG, "setUp: currentUser=" + mCurrentUser); + // Create a new controller for the current user and start listening for changes. mController = new NightDisplayController(getContext(), mCurrentUser); mController.setListener(this); @@ -196,6 +198,8 @@ public final class NightDisplayService extends SystemService } private void tearDown() { + Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser); + if (mController != null) { mController.setListener(null); mController = null; @@ -273,6 +277,8 @@ public final class NightDisplayService extends SystemService @Override public void onAutoModeChanged(int autoMode) { + Slog.d(TAG, "onAutoModeChanged: autoMode=" + autoMode); + if (mAutoMode != null) { mAutoMode.onStop(); mAutoMode = null; @@ -291,6 +297,8 @@ public final class NightDisplayService extends SystemService @Override public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) { + Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime); + if (mAutoMode != null) { mAutoMode.onCustomStartTimeChanged(startTime); } @@ -298,6 +306,8 @@ public final class NightDisplayService extends SystemService @Override public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) { + Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime); + if (mAutoMode != null) { mAutoMode.onCustomEndTimeChanged(endTime); } @@ -419,7 +429,7 @@ public final class NightDisplayService extends SystemService @Override public void onAlarm() { - if (DEBUG) Slog.d(TAG, "onAlarm"); + Slog.d(TAG, "onAlarm"); updateActivated(); } } @@ -477,7 +487,8 @@ public final class NightDisplayService extends SystemService @Override public void onTwilightStateChanged(@Nullable TwilightState state) { - if (DEBUG) Slog.d(TAG, "onTwilightStateChanged"); + Slog.d(TAG, "onTwilightStateChanged: isNight=" + + (state == null ? null : state.isNight())); updateActivated(state); } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 777eee81473a..697186e562fb 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -78,6 +78,8 @@ import java.util.List; public class MediaSessionService extends SystemService implements Monitor { private static final String TAG = "MediaSessionService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + // Leave log for media key event always. + private static final boolean DEBUG_MEDIA_KEY_EVENT = DEBUG || true; private static final int WAKELOCK_TIMEOUT = 5000; @@ -302,7 +304,7 @@ public class MediaSessionService extends SystemService implements Monitor { */ private void destroySessionLocked(MediaSessionRecord session) { if (DEBUG) { - Log.d(TAG, "Destroying session : " + session.toString()); + Log.d(TAG, "Destroying " + session); } int userId = session.getUserId(); UserRecord user = mUserRecords.get(userId); @@ -408,7 +410,7 @@ public class MediaSessionService extends SystemService implements Monitor { if (component != null) { if (compName.equals(component)) { if (DEBUG) { - Log.d(TAG, "ok to get sessions: " + component + + Log.d(TAG, "ok to get sessions. " + component + " is authorized notification listener"); } return true; @@ -417,7 +419,7 @@ public class MediaSessionService extends SystemService implements Monitor { } } if (DEBUG) { - Log.d(TAG, "not ok to get sessions, " + compName + + Log.d(TAG, "not ok to get sessions. " + compName + " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId); } } @@ -462,7 +464,7 @@ public class MediaSessionService extends SystemService implements Monitor { mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0); if (DEBUG) { - Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag); + Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag); } return session; } @@ -881,17 +883,16 @@ public class MediaSessionService extends SystemService implements Monitor { private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags, MediaSessionRecord session) { - if (DEBUG) { - String description = session == null ? null : session.toString(); - Log.d(TAG, "Adjusting session " + description + " by " + direction + ". flags=" - + flags + ", suggestedStream=" + suggestedStream); - - } boolean preferSuggestedStream = false; if (isValidLocalStreamType(suggestedStream) && AudioSystem.isStreamActive(suggestedStream, 0)) { preferSuggestedStream = true; } + if (DEBUG) { + Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags=" + + flags + ", suggestedStream=" + suggestedStream + + ", preferSuggestedStream=" + preferSuggestedStream); + } if (session == null || preferSuggestedStream) { if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) { @@ -946,8 +947,8 @@ public class MediaSessionService extends SystemService implements Monitor { private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, MediaSessionRecord session) { if (session != null) { - if (DEBUG) { - Log.d(TAG, "Sending media key to " + session.toString()); + if (DEBUG_MEDIA_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to " + session); } if (needWakeLock) { mKeyEventReceiver.aquireWakeLockLocked(); @@ -966,11 +967,6 @@ public class MediaSessionService extends SystemService implements Monitor { && user.mRestoredMediaButtonReceiver == null) { continue; } - if (DEBUG) { - Log.d(TAG, "Sending media key to last known PendingIntent " - + user.mLastMediaButtonReceiver + " or restored Intent " - + user.mRestoredMediaButtonReceiver); - } if (needWakeLock) { mKeyEventReceiver.aquireWakeLockLocked(); } @@ -979,10 +975,19 @@ public class MediaSessionService extends SystemService implements Monitor { mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); try { if (user.mLastMediaButtonReceiver != null) { + if (DEBUG_MEDIA_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + + " to the last known pendingIntent " + + user.mLastMediaButtonReceiver); + } user.mLastMediaButtonReceiver.send(getContext(), needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mediaButtonIntent, mKeyEventReceiver, mHandler); } else { + if (DEBUG_MEDIA_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the restored intent " + + user.mRestoredMediaButtonReceiver); + } mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver); getContext().sendBroadcastAsUser(mediaButtonIntent, UserHandle.of(userId)); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 6a56fa6e629b..d25abbf1ff8a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -870,13 +870,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { IntentSender statusReceiver, int userId) { final int callingUid = Binder.getCallingUid(); mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall"); - boolean allowSilentUninstall = true; if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) { mAppOps.checkPackage(callingUid, callerPackageName); - final String installerPackageName = mPm.getInstallerPackageName(packageName); - allowSilentUninstall = mPm.isOrphaned(packageName) || - (installerPackageName != null - && installerPackageName.equals(callerPackageName)); } // Check whether the caller is device owner, in which case we do it silently. @@ -887,8 +882,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, statusReceiver, packageName, isDeviceOwner, userId); - if (allowSilentUninstall && mContext.checkCallingOrSelfPermission( - android.Manifest.permission.DELETE_PACKAGES) == PackageManager.PERMISSION_GRANTED) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES) + == PackageManager.PERMISSION_GRANTED) { // Sweet, call straight through! mPm.deletePackage(packageName, adapter.getBinder(), userId, flags); } else if (isDeviceOwner) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 6cdc40f7a81c..583128444cfc 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -109,6 +109,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final int installerUid; final SessionParams params; final long createdMillis; + final int defaultContainerGid; /** Staging location where client data is written. */ final File stageDir; @@ -199,13 +200,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { + // Cache package manager data without the lock held + final PackageInfo pkgInfo = mPm.getPackageInfo( + params.appPackageName, PackageManager.GET_SIGNATURES /*flags*/, userId); + final ApplicationInfo appInfo = mPm.getApplicationInfo( + params.appPackageName, 0, userId); + synchronized (mLock) { if (msg.obj != null) { mRemoteObserver = (IPackageInstallObserver2) msg.obj; } try { - commitLocked(); + commitLocked(pkgInfo, appInfo); } catch (PackageManagerException e) { final String completeMsg = ExceptionUtils.getCompleteMessage(e); Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); @@ -264,6 +271,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } else { mPermissionsAccepted = false; } + final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE, + PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); + defaultContainerGid = UserHandle.getSharedAppGid(uid); } public SessionInfo generateInfo() { @@ -520,7 +530,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); } - private void commitLocked() throws PackageManagerException { + private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo) + throws PackageManagerException { if (mDestroyed) { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); } @@ -538,7 +549,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Verify that stage looks sane with respect to existing application. // This currently only ensures packageName, versionCode, and certificate // consistency. - validateInstallLocked(); + validateInstallLocked(pkgInfo, appInfo); Preconditions.checkNotNull(mPackageName); Preconditions.checkNotNull(mSignatures); @@ -650,7 +661,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Note that upgrade compatibility is still performed by * {@link PackageManagerService}. */ - private void validateInstallLocked() throws PackageManagerException { + private void validateInstallLocked(PackageInfo pkgInfo, ApplicationInfo appInfo) + throws PackageManagerException { mPackageName = null; mVersionCode = -1; mSignatures = null; @@ -729,10 +741,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (removeSplitList.size() > 0) { // validate split names marked for removal - final int flags = mSignatures == null ? PackageManager.GET_SIGNATURES : 0; - final PackageInfo pkg = mPm.getPackageInfo(params.appPackageName, flags, userId); for (String splitName : removeSplitList) { - if (!ArrayUtils.contains(pkg.splitNames, splitName)) { + if (!ArrayUtils.contains(pkgInfo.splitNames, splitName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Split not found: " + splitName); } @@ -740,11 +750,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // ensure we've got appropriate package name, version code and signatures if (mPackageName == null) { - mPackageName = pkg.packageName; - mVersionCode = pkg.versionCode; + mPackageName = pkgInfo.packageName; + mVersionCode = pkgInfo.versionCode; } if (mSignatures == null) { - mSignatures = pkg.signatures; + mSignatures = pkgInfo.signatures; } } @@ -757,8 +767,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } else { // Partial installs must be consistent with existing install - final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId); - if (app == null) { + if (appInfo == null) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Missing existing base package for " + mPackageName); } @@ -766,8 +775,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final PackageLite existing; final ApkLite existingBase; try { - existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0); - existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()), + existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0); + existingBase = PackageParser.parseApkLite(new File(appInfo.getBaseCodePath()), PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw PackageManagerException.from(e); @@ -777,7 +786,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Inherit base if not overridden if (mResolvedBaseFile == null) { - mResolvedBaseFile = new File(app.getBaseCodePath()); + mResolvedBaseFile = new File(appInfo.getBaseCodePath()); mResolvedInheritedFiles.add(mResolvedBaseFile); } @@ -794,7 +803,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // Inherit compiled oat directory. - final File packageInstallDir = (new File(app.getBaseCodePath())).getParentFile(); + final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile(); mInheritedFilesBase = packageInstallDir; final File oatDir = new File(packageInstallDir, "oat"); if (oatDir.exists()) { @@ -822,7 +831,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException { + private void assertApkConsistent(String tag, ApkLite apk) + throws PackageManagerException { if (!mPackageName.equals(apk.packageName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " + apk.packageName + " inconsistent with " + mPackageName); @@ -1035,10 +1045,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Failed to finalize container " + cid); } - final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE, - PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); - final int gid = UserHandle.getSharedAppGid(uid); - if (!PackageHelper.fixSdPermissions(cid, gid, null)) { + if (!PackageHelper.fixSdPermissions(cid, defaultContainerGid, null)) { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to fix permissions on container " + cid); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f326555ee00a..f84356bdb2b7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -177,6 +177,7 @@ import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.PatternMatcher; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -196,6 +197,7 @@ import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; import android.provider.Settings.Global; +import android.provider.Settings.Secure; import android.security.KeyStore; import android.security.SystemKeyStore; import android.system.ErrnoException; @@ -363,6 +365,7 @@ public class PackageManagerService extends IPackageManager.Stub { static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false; + // STOPSHIP; b/30256615 private static final boolean DISABLE_EPHEMERAL_APPS = !Build.IS_DEBUGGABLE; private static final int RADIO_UID = Process.PHONE_UID; @@ -531,6 +534,7 @@ public class PackageManagerService extends IPackageManager.Stub { final String[] mSeparateProcesses; final boolean mIsUpgrade; final boolean mIsPreNUpgrade; + final boolean mIsPreNMR1Upgrade; /** The location for ASEC container files on internal storage. */ final String mAsecInternalPath; @@ -2238,6 +2242,8 @@ public class PackageManagerService extends IPackageManager.Stub { // as there is no profiling data available. mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N; + mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1; + // save off the names of pre-existing system packages prior to scanning; we don't // want to automatically grant runtime permissions for new system apps if (mPromoteSystemApps) { @@ -4722,20 +4728,6 @@ public class PackageManagerService extends IPackageManager.Stub { final ResolveInfo bestChoice = chooseBestActivity(intent, resolvedType, flags, query, userId); - - if (isEphemeralAllowed(intent, query, userId)) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral"); - final EphemeralResolveInfo ai = - getEphemeralResolveInfo(intent, resolvedType, userId); - if (ai != null) { - if (DEBUG_EPHEMERAL) { - Slog.v(TAG, "Returning an EphemeralResolveInfo"); - } - bestChoice.ephemeralInstaller = mEphemeralInstallerInfo; - bestChoice.ephemeralResolveInfo = ai; - } - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } return bestChoice; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -4776,11 +4768,23 @@ public class PackageManagerService extends IPackageManager.Stub { false, false, false, userId); } + private boolean isEphemeralDisabled() { + // ephemeral apps have been disabled across the board + if (DISABLE_EPHEMERAL_APPS) { + return true; + } + // system isn't up yet; can't read settings, so, assume no ephemeral apps + if (!mSystemReady) { + return true; + } + return Secure.getInt(mContext.getContentResolver(), Secure.WEB_ACTION_ENABLED, 1) == 0; + } private boolean isEphemeralAllowed( - Intent intent, List<ResolveInfo> resolvedActivites, int userId) { + Intent intent, List<ResolveInfo> resolvedActivities, int userId, + boolean skipPackageCheck) { // Short circuit and return early if possible. - if (DISABLE_EPHEMERAL_APPS) { + if (isEphemeralDisabled()) { return false; } final int callingUser = UserHandle.getCallingUserId(); @@ -4793,18 +4797,21 @@ public class PackageManagerService extends IPackageManager.Stub { if (intent.getComponent() != null) { return false; } - if (intent.getPackage() != null) { + if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) { + return false; + } + if (!skipPackageCheck && intent.getPackage() != null) { return false; } final boolean isWebUri = hasWebURI(intent); - if (!isWebUri) { + if (!isWebUri || intent.getData().getHost() == null) { return false; } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. synchronized (mPackages) { - final int count = resolvedActivites.size(); + final int count = (resolvedActivities == null ? 0 : resolvedActivities.size()); for (int n = 0; n < count; n++) { - ResolveInfo info = resolvedActivites.get(n); + ResolveInfo info = resolvedActivities.get(n); String packageName = info.activityInfo.packageName; PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { @@ -4826,19 +4833,19 @@ public class PackageManagerService extends IPackageManager.Stub { return true; } - private EphemeralResolveInfo getEphemeralResolveInfo(Intent intent, String resolvedType, - int userId) { - final int ephemeralPrefixMask = Global.getInt(mContext.getContentResolver(), + private static EphemeralResolveInfo getEphemeralResolveInfo( + Context context, EphemeralResolverConnection resolverConnection, Intent intent, + String resolvedType, int userId, String packageName) { + final int ephemeralPrefixMask = Global.getInt(context.getContentResolver(), Global.EPHEMERAL_HASH_PREFIX_MASK, DEFAULT_EPHEMERAL_HASH_PREFIX_MASK); - final int ephemeralPrefixCount = Global.getInt(mContext.getContentResolver(), + final int ephemeralPrefixCount = Global.getInt(context.getContentResolver(), Global.EPHEMERAL_HASH_PREFIX_COUNT, DEFAULT_EPHEMERAL_HASH_PREFIX_COUNT); final EphemeralDigest digest = new EphemeralDigest(intent.getData(), ephemeralPrefixMask, ephemeralPrefixCount); final int[] shaPrefix = digest.getDigestPrefix(); final byte[][] digestBytes = digest.getDigestBytes(); final List<EphemeralResolveInfo> ephemeralResolveInfoList = - mEphemeralResolverConnection.getEphemeralResolveInfoList( - shaPrefix, ephemeralPrefixMask); + resolverConnection.getEphemeralResolveInfoList(shaPrefix, ephemeralPrefixMask); if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) { // No hash prefix match; there are no ephemeral apps for this domain. return null; @@ -4855,6 +4862,10 @@ public class PackageManagerService extends IPackageManager.Stub { if (filters.isEmpty()) { continue; } + if (packageName != null + && !packageName.equals(ephemeralApplication.getPackageName())) { + continue; + } // We have a domain match; resolve the filters to see if anything matches. final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver(); for (int j = filters.size() - 1; j >= 0; --j) { @@ -5258,8 +5269,12 @@ public class PackageManagerService extends IPackageManager.Stub { } // reader + boolean sortResult = false; + boolean addEphemeral = false; + boolean matchEphemeralPackage = false; + List<ResolveInfo> result; + final String pkgName = intent.getPackage(); synchronized (mPackages) { - final String pkgName = intent.getPackage(); if (pkgName == null) { List<CrossProfileIntentFilter> matchingFilters = getMatchingCrossProfileIntentFilters(intent, resolvedType, userId); @@ -5267,15 +5282,16 @@ public class PackageManagerService extends IPackageManager.Stub { ResolveInfo xpResolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent, resolvedType, flags, userId); if (xpResolveInfo != null) { - List<ResolveInfo> result = new ArrayList<ResolveInfo>(1); - result.add(xpResolveInfo); - return filterIfNotSystemUser(result, userId); + List<ResolveInfo> xpResult = new ArrayList<ResolveInfo>(1); + xpResult.add(xpResolveInfo); + return filterIfNotSystemUser(xpResult, userId); } // Check for results in the current profile. - List<ResolveInfo> result = mActivities.queryIntent( - intent, resolvedType, flags, userId); - result = filterIfNotSystemUser(result, userId); + result = filterIfNotSystemUser(mActivities.queryIntent( + intent, resolvedType, flags, userId), userId); + addEphemeral = + isEphemeralAllowed(intent, result, userId, false /*skipPackageCheck*/); // Check for cross profile results. boolean hasNonNegativePriorityResult = hasNonNegativePriority(result); @@ -5287,7 +5303,7 @@ public class PackageManagerService extends IPackageManager.Stub { Collections.singletonList(xpResolveInfo), userId).size() > 0; if (isVisibleToUser) { result.add(xpResolveInfo); - Collections.sort(result, mResolvePrioritySorter); + sortResult = true; } } if (hasWebURI(intent)) { @@ -5303,28 +5319,61 @@ public class PackageManagerService extends IPackageManager.Stub { // in the result. result.remove(xpResolveInfo); } - if (result.size() == 0) { + if (result.size() == 0 && !addEphemeral) { result.add(xpDomainInfo.resolveInfo); return result; } - } else if (result.size() <= 1) { - return result; } - result = filterCandidatesWithDomainPreferredActivitiesLPr(intent, flags, result, - xpDomainInfo, userId); - Collections.sort(result, mResolvePrioritySorter); + if (result.size() > 1 || addEphemeral) { + result = filterCandidatesWithDomainPreferredActivitiesLPr( + intent, flags, result, xpDomainInfo, userId); + sortResult = true; + } + } + } else { + final PackageParser.Package pkg = mPackages.get(pkgName); + if (pkg != null) { + result = filterIfNotSystemUser( + mActivities.queryIntentForPackage( + intent, resolvedType, flags, pkg.activities, userId), + userId); + } else { + // the caller wants to resolve for a particular package; however, there + // were no installed results, so, try to find an ephemeral result + addEphemeral = isEphemeralAllowed( + intent, null /*result*/, userId, true /*skipPackageCheck*/); + matchEphemeralPackage = true; + result = new ArrayList<ResolveInfo>(); } - return result; } - final PackageParser.Package pkg = mPackages.get(pkgName); - if (pkg != null) { - return filterIfNotSystemUser( - mActivities.queryIntentForPackage( - intent, resolvedType, flags, pkg.activities, userId), - userId); + } + if (addEphemeral) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral"); + final EphemeralResolveInfo ai = getEphemeralResolveInfo( + mContext, mEphemeralResolverConnection, intent, resolvedType, userId, + matchEphemeralPackage ? pkgName : null); + if (ai != null) { + if (DEBUG_EPHEMERAL) { + Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); + } + final ResolveInfo ephemeralInstaller = new ResolveInfo(mEphemeralInstallerInfo); + ephemeralInstaller.ephemeralResolveInfo = ai; + // make sure this resolver is the default + ephemeralInstaller.isDefault = true; + ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART + | IntentFilter.MATCH_ADJUSTMENT_NORMAL; + // add a non-generic filter + ephemeralInstaller.filter = new IntentFilter(intent.getAction()); + ephemeralInstaller.filter.addDataPath( + intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); + result.add(ephemeralInstaller); } - return new ArrayList<ResolveInfo>(); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + if (sortResult) { + Collections.sort(result, mResolvePrioritySorter); + } + return result; } private static class CrossProfileDomainInfo { @@ -6204,7 +6253,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public ParceledListSlice<EphemeralApplicationInfo> getEphemeralApplications(int userId) { - if (DISABLE_EPHEMERAL_APPS) { + if (isEphemeralDisabled()) { return null; } @@ -6228,7 +6277,7 @@ public class PackageManagerService extends IPackageManager.Stub { enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */, false /* checkShell */, "isEphemeral"); - if (DISABLE_EPHEMERAL_APPS) { + if (isEphemeralDisabled()) { return false; } @@ -6246,7 +6295,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public byte[] getEphemeralApplicationCookie(String packageName, int userId) { - if (DISABLE_EPHEMERAL_APPS) { + if (isEphemeralDisabled()) { return null; } @@ -6264,7 +6313,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public boolean setEphemeralApplicationCookie(String packageName, byte[] cookie, int userId) { - if (DISABLE_EPHEMERAL_APPS) { + if (isEphemeralDisabled()) { return true; } @@ -6282,7 +6331,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public Bitmap getEphemeralApplicationIcon(String packageName, int userId) { - if (DISABLE_EPHEMERAL_APPS) { + if (isEphemeralDisabled()) { return null; } @@ -6612,9 +6661,13 @@ public class PackageManagerService extends IPackageManager.Stub { private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile, final int policyFlags) throws PackageManagerException { + // When upgrading from pre-N MR1, verify the package time stamp using the package + // directory and not the APK file. + final long lastModifiedTime = mIsPreNMR1Upgrade + ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg, srcFile); if (ps != null && ps.codePath.equals(srcFile) - && ps.timeStamp == getLastModifiedTime(pkg, srcFile) + && ps.timeStamp == lastModifiedTime && !isCompatSignatureUpdateNeeded(pkg) && !isRecoverSignatureUpdateNeeded(pkg)) { long mSigningKeySetId = ps.keySetData.getProperSigningKeySet(); @@ -6636,7 +6689,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them."); } else { - Log.i(TAG, srcFile.toString() + " changed; collecting certs"); + Slog.i(TAG, srcFile.toString() + " changed; collecting certs"); } try { @@ -8366,6 +8419,10 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST; } + if (isSystemApp(pkg)) { + pkgSetting.isOrphaned = true; + } + ArrayList<PackageParser.Package> clientLibPkgs = null; if ((scanFlags & SCAN_CHECK_ONLY) != 0) { @@ -8649,7 +8706,9 @@ public class PackageManagerService extends IPackageManager.Stub { for (i=0; i<N; i++) { PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i); PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name); - if (cur == null) { + final String curPackageName = cur == null ? null : cur.info.packageName; + final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName); + if (cur == null || isPackageUpdate) { mPermissionGroups.put(pg.info.name, pg); if ((policyFlags&PackageParser.PARSE_CHATTY) != 0) { if (r == null) { @@ -8657,6 +8716,9 @@ public class PackageManagerService extends IPackageManager.Stub { } else { r.append(' '); } + if (isPackageUpdate) { + r.append("UPD:"); + } r.append(pg.info.name); } } else { @@ -9189,15 +9251,17 @@ public class PackageManagerService extends IPackageManager.Stub { mEphemeralInstallerActivity.packageName = pkg.applicationInfo.packageName; mEphemeralInstallerActivity.processName = pkg.applicationInfo.packageName; mEphemeralInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; - mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | - ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; + mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS + | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; mEphemeralInstallerActivity.theme = 0; mEphemeralInstallerActivity.exported = true; mEphemeralInstallerActivity.enabled = true; mEphemeralInstallerInfo.activityInfo = mEphemeralInstallerActivity; mEphemeralInstallerInfo.priority = 0; - mEphemeralInstallerInfo.preferredOrder = 0; - mEphemeralInstallerInfo.match = 0; + mEphemeralInstallerInfo.preferredOrder = 1; + mEphemeralInstallerInfo.isDefault = true; + mEphemeralInstallerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART + | IntentFilter.MATCH_ADJUSTMENT_NORMAL; if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Set ephemeral installer activity: " + mEphemeralInstallerComponent); @@ -15310,6 +15374,19 @@ public class PackageManagerService extends IPackageManager.Stub { Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(observer); final int uid = Binder.getCallingUid(); + if (uid != Process.SHELL_UID && uid != Process.ROOT_UID && uid != Process.SYSTEM_UID + && uid != getPackageUid(mRequiredInstallerPackage, 0, UserHandle.getUserId(uid)) + && !isOrphaned(packageName) + && !isCallerSameAsInstaller(uid, packageName)) { + try { + final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE); + intent.setData(Uri.fromParts("package", packageName, null)); + intent.putExtra(PackageInstaller.EXTRA_CALLBACK, observer.asBinder()); + observer.onUserActionRequired(intent); + } catch (RemoteException re) { + } + return; + } final boolean deleteAllUsers = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0; final int[] users = deleteAllUsers ? sUserManager.getUserIds() : new int[]{ userId }; if (UserHandle.getUserId(uid) != userId || (deleteAllUsers && users.length > 1)) { @@ -15378,6 +15455,12 @@ public class PackageManagerService extends IPackageManager.Stub { }); } + private boolean isCallerSameAsInstaller(int callingUid, String pkgName) { + final int installerPkgUid = getPackageUid(getInstallerPackageName(pkgName), + 0 /* flags */, UserHandle.getUserId(callingUid)); + return installerPkgUid == callingUid; + } + private int[] getBlockUninstallForUsers(String packageName, int[] userIds) { int[] result = EMPTY_INT_ARRAY; for (int userId : userIds) { diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 827b88a5b61a..1acc9552c0a1 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -667,6 +667,12 @@ class ShortcutPackage extends ShortcutPackageItem { // - version code hasn't change // - lastUpdateTime hasn't change // - all target activities are still enabled. + + // Note, system apps timestamps do *not* change after OTAs. (But they do + // after an adb sync or a local flash.) + // This means if a system app's version code doesn't change on an OTA, + // we don't notice it's updated. But that's fine since their version code *should* + // really change on OTAs. if ((getPackageInfo().getVersionCode() == pi.versionCode) && (getPackageInfo().getLastUpdateTime() == pi.lastUpdateTime) && areAllActivitiesStillEnabled()) { @@ -1145,6 +1151,17 @@ class ShortcutPackage extends ShortcutPackageItem { } } + /** @return true if there's any shortcuts that are not manifest shortcuts. */ + public boolean hasNonManifestShortcuts() { + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + final ShortcutInfo si = mShortcuts.valueAt(i); + if (!si.isDeclaredInManifest()) { + return true; + } + } + return false; + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { pw.println(); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 79b5c4eae1dc..178005840ab1 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -40,7 +40,7 @@ abstract class ShortcutPackageItem { private final ShortcutPackageInfo mPackageInfo; - protected final ShortcutUser mShortcutUser; + protected ShortcutUser mShortcutUser; protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser, int packageUserId, @NonNull String packageName, @@ -51,6 +51,13 @@ abstract class ShortcutPackageItem { mPackageInfo = Preconditions.checkNotNull(packageInfo); } + /** + * Change the parent {@link ShortcutUser}. Need it in the restore code. + */ + public void replaceUser(ShortcutUser user) { + mShortcutUser = user; + } + public ShortcutUser getUser() { return mShortcutUser; } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c1fc7f114c67..2c61f75bd426 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -54,6 +54,7 @@ import android.graphics.RectF; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; @@ -324,9 +325,29 @@ public class ShortcutService extends IShortcutService.Stub { int CHECK_LAUNCHER_ACTIVITY = 12; int IS_ACTIVITY_ENABLED = 13; int PACKAGE_UPDATE_CHECK = 14; - - int COUNT = PACKAGE_UPDATE_CHECK + 1; - } + int ASYNC_PRELOAD_USER_DELAY = 15; + + int COUNT = ASYNC_PRELOAD_USER_DELAY + 1; + } + + private static final String[] STAT_LABELS = { + "getHomeActivities()", + "Launcher permission check", + "getPackageInfo()", + "getPackageInfo(SIG)", + "getApplicationInfo", + "cleanupDanglingBitmaps", + "getActivity+metadata", + "getInstalledPackages", + "checkPackageChanges", + "getApplicationResources", + "resourceNameLookup", + "getLauncherActivity", + "checkLauncherActivity", + "isActivityEnabled", + "packageUpdateCheck", + "asyncPreloadUserDelay" + }; final Object mStatLock = new Object(); @@ -359,6 +380,12 @@ public class ShortcutService extends IShortcutService.Stub { @GuardedBy("mLock") private Exception mLastWtfStacktrace; + static class InvalidFileFormatException extends Exception { + public InvalidFileFormatException(String message, Throwable cause) { + super(message, cause); + } + } + public ShortcutService(Context context) { this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false); } @@ -533,19 +560,26 @@ public class ShortcutService extends IShortcutService.Stub { /** lifecycle event */ void handleUnlockUser(int userId) { if (DEBUG) { - Slog.d(TAG, "handleUnlockUser: user=" + userId); + Slog.d(TAG, "handleUnlockUser: user=" + userId); } synchronized (mLock) { mUnlockedUsers.put(userId, true); - - // Preload the user's shortcuts. - // Also see if the locale has changed. - // Note as of nyc, the locale is per-user, so the locale shouldn't change - // when the user is locked. However due to b/30119489 it still happens. - getUserShortcutsLocked(userId).detectLocaleChange(); - - checkPackageChanges(userId); } + + // Preload the user data. + // Note, we don't use mHandler here but instead just start a new thread. + // This is because mHandler (which uses com.android.internal.os.BackgroundThread) is very + // busy at this point and this could take hundreds of milliseconds, which would be too + // late since the launcher would already have started. + // So we just create a new thread. This code runs rarely, so we don't use a thread pool + // or anything. + final long start = injectElapsedRealtime(); + injectRunOnNewThread(() -> { + synchronized (mLock) { + logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start); + getUserShortcutsLocked(userId); + } + }); } /** lifecycle event */ @@ -933,7 +967,7 @@ public class ShortcutService extends IShortcutService.Stub { try { final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false); return ret; - } catch (IOException | XmlPullParserException e) { + } catch (IOException | XmlPullParserException | InvalidFileFormatException e) { Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); return null; } finally { @@ -942,7 +976,8 @@ public class ShortcutService extends IShortcutService.Stub { } private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, - boolean fromBackup) throws XmlPullParserException, IOException { + boolean fromBackup) throws XmlPullParserException, IOException, + InvalidFileFormatException { final BufferedInputStream bis = new BufferedInputStream(is); @@ -1110,6 +1145,9 @@ public class ShortcutService extends IShortcutService.Stub { userPackages = new ShortcutUser(this, userId); } mUsers.put(userId, userPackages); + + // Also when a user's data is first accessed, scan all packages. + checkPackageChanges(userId); } return userPackages; } @@ -1468,6 +1506,10 @@ public class ShortcutService extends IShortcutService.Stub { mHandler.post(r); } + void injectRunOnNewThread(Runnable r) { + new Thread(r).start(); + } + /** * @throws IllegalArgumentException if {@code numShortcuts} is bigger than * {@link #getMaxActivityShortcuts()}. @@ -2625,10 +2667,14 @@ public class ShortcutService extends IShortcutService.Stub { boolean forceRescan) { final ShortcutUser user = getUserShortcutsLocked(userId); + // Note after each OTA, we'll need to rescan all system apps, as their lastUpdateTime + // is not reliable. final long now = injectCurrentTimeMillis(); + final boolean afterOta = + !injectBuildFingerprint().equals(user.getLastAppScanOsFingerprint()); // Then for each installed app, publish manifest shortcuts when needed. - forUpdatedPackages(userId, lastScanTime, ai -> { + forUpdatedPackages(userId, lastScanTime, afterOta, ai -> { user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId); user.rescanPackageIfNeeded(ai.packageName, forceRescan); }); @@ -2636,6 +2682,7 @@ public class ShortcutService extends IShortcutService.Stub { // Write the time just before the scan, because there may be apps that have just // been updated, and we want to catch them in the next time. user.setLastAppScanTime(now); + user.setLastAppScanOsFingerprint(injectBuildFingerprint()); scheduleSaveUser(userId); } @@ -2874,7 +2921,7 @@ public class ShortcutService extends IShortcutService.Stub { return parceledList.getList(); } - private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, + private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta, Consumer<ApplicationInfo> callback) { if (DEBUG) { Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime); @@ -2886,7 +2933,8 @@ public class ShortcutService extends IShortcutService.Stub { // If the package has been updated since the last scan time, then scan it. // Also if it's a system app with no update, lastUpdateTime is not reliable, so // just scan it. - if (pi.lastUpdateTime >= lastScanTime || isPureSystemApp(pi.applicationInfo)) { + if (pi.lastUpdateTime >= lastScanTime + || (afterOta && isPureSystemApp(pi.applicationInfo))) { if (DEBUG) { Slog.d(TAG, "Found updated package " + pi.packageName); } @@ -3129,15 +3177,16 @@ public class ShortcutService extends IShortcutService.Stub { wtf("Can't restore: user " + userId + " is locked or not running"); return; } - final ShortcutUser user; + // Actually do restore. + final ShortcutUser restored; final ByteArrayInputStream is = new ByteArrayInputStream(payload); try { - user = loadUserInternal(userId, is, /* fromBackup */ true); - } catch (XmlPullParserException | IOException e) { + restored = loadUserInternal(userId, is, /* fromBackup */ true); + } catch (XmlPullParserException | IOException | InvalidFileFormatException e) { Slog.w(TAG, "Restoration failed.", e); return; } - mUsers.put(userId, user); + getUserShortcutsLocked(userId).mergeRestoredFile(restored); // Rescan all packages to re-publish manifest shortcuts and do other checks. rescanUpdatedPackagesLocked(userId, @@ -3218,23 +3267,9 @@ public class ShortcutService extends IShortcutService.Stub { pw.println(" Stats:"); synchronized (mStatLock) { - final String p = " "; - dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()"); - dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check"); - - dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()"); - dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)"); - dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo"); - dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps"); - dumpStatLS(pw, p, Stats.GET_ACTIVITY_WITH_METADATA, "getActivity+metadata"); - dumpStatLS(pw, p, Stats.GET_INSTALLED_PACKAGES, "getInstalledPackages"); - dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges"); - dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources"); - dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup"); - dumpStatLS(pw, p, Stats.GET_LAUNCHER_ACTIVITY, "getLauncherActivity"); - dumpStatLS(pw, p, Stats.CHECK_LAUNCHER_ACTIVITY, "checkLauncherActivity"); - dumpStatLS(pw, p, Stats.IS_ACTIVITY_ENABLED, "isActivityEnabled"); - dumpStatLS(pw, p, Stats.PACKAGE_UPDATE_CHECK, "packageUpdateCheck"); + for (int i = 0; i < Stats.COUNT; i++) { + dumpStatLS(pw, " ", i); + } } pw.println(); @@ -3277,12 +3312,12 @@ public class ShortcutService extends IShortcutService.Stub { return tobj.format("%Y-%m-%d %H:%M:%S"); } - private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) { + private void dumpStatLS(PrintWriter pw, String prefix, int statId) { pw.print(prefix); final int count = mCountStats[statId]; final long dur = mDurationStats[statId]; pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms", - label, count, dur, + STAT_LABELS[statId], count, dur, (count == 0 ? 0 : ((double) dur) / count))); } @@ -3578,6 +3613,12 @@ public class ShortcutService extends IShortcutService.Stub { Binder.restoreCallingIdentity(token); } + // Injection point. + @VisibleForTesting + String injectBuildFingerprint() { + return Build.FINGERPRINT; + } + final void wtf(String message) { wtf(message, /* exception= */ null); } diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index ce3ed9c7deef..5d4bfa4dcb3e 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -23,12 +23,14 @@ import android.content.pm.ShortcutManager; import android.text.TextUtils; import android.text.format.Formatter; import android.util.ArrayMap; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.server.pm.ShortcutService.InvalidFileFormatException; import libcore.util.Objects; @@ -60,6 +62,7 @@ class ShortcutUser { // Suffix "2" was added to force rescan all packages after the next OTA. private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time2"; + private static final String ATTR_LAST_APP_SCAN_OS_FINGERPRINT = "last-app-scan-fp"; private static final String KEY_USER_ID = "userId"; private static final String KEY_LAUNCHERS = "launchers"; private static final String KEY_PACKAGES = "packages"; @@ -125,6 +128,8 @@ class ShortcutUser { private long mLastAppScanTime; + private String mLastAppScanOsFingerprint; + public ShortcutUser(ShortcutService service, int userId) { mService = service; mUserId = userId; @@ -142,6 +147,14 @@ class ShortcutUser { mLastAppScanTime = lastAppScanTime; } + public String getLastAppScanOsFingerprint() { + return mLastAppScanOsFingerprint; + } + + public void setLastAppScanOsFingerprint(String lastAppScanOsFingerprint) { + mLastAppScanOsFingerprint = lastAppScanOsFingerprint; + } + // We don't expose this directly to non-test code because only ShortcutUser should add to/ // remove from it. @VisibleForTesting @@ -153,6 +166,11 @@ class ShortcutUser { return mPackages.containsKey(packageName); } + private void addPackage(@NonNull ShortcutPackage p) { + p.replaceUser(this); + mPackages.put(p.getPackageName(), p); + } + public ShortcutPackage removePackage(@NonNull String packageName) { final ShortcutPackage removed = mPackages.remove(packageName); @@ -168,7 +186,8 @@ class ShortcutUser { return mLaunchers; } - public void addLauncher(ShortcutLauncher launcher) { + private void addLauncher(ShortcutLauncher launcher) { + launcher.replaceUser(this); mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), launcher.getPackageName()), launcher); } @@ -315,11 +334,16 @@ class ShortcutUser { throws IOException, XmlPullParserException { out.startTag(null, TAG_ROOT); - ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales); - ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, - mLastAppScanTime); + if (!forBackup) { + // Don't have to back them up. + ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales); + ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, + mLastAppScanTime); + ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT, + mLastAppScanOsFingerprint); - ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher); + ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher); + } // Can't use forEachPackageItem due to the checked exceptions. { @@ -352,53 +376,59 @@ class ShortcutUser { } public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, - boolean fromBackup) throws IOException, XmlPullParserException { + boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException { final ShortcutUser ret = new ShortcutUser(s, userId); - ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, - ATTR_KNOWN_LOCALES); - - // If lastAppScanTime is in the future, that means the clock went backwards. - // Just scan all apps again. - final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, - ATTR_LAST_APP_SCAN_TIME); - final long currentTime = s.injectCurrentTimeMillis(); - ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; - - final int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final int depth = parser.getDepth(); - final String tag = parser.getName(); - - if (depth == outerDepth + 1) { - switch (tag) { - case TAG_LAUNCHER: { - ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute( - parser, ATTR_VALUE); - continue; - } - case ShortcutPackage.TAG_ROOT: { - final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( - s, ret, parser, fromBackup); - - // Don't use addShortcut(), we don't need to save the icon. - ret.mPackages.put(shortcuts.getPackageName(), shortcuts); - continue; - } - - case ShortcutLauncher.TAG_ROOT: { - ret.addLauncher( - ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); - continue; + try { + ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, + ATTR_KNOWN_LOCALES); + + // If lastAppScanTime is in the future, that means the clock went backwards. + // Just scan all apps again. + final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, + ATTR_LAST_APP_SCAN_TIME); + final long currentTime = s.injectCurrentTimeMillis(); + ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; + ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser, + ATTR_LAST_APP_SCAN_OS_FINGERPRINT); + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + + if (depth == outerDepth + 1) { + switch (tag) { + case TAG_LAUNCHER: { + ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute( + parser, ATTR_VALUE); + continue; + } + case ShortcutPackage.TAG_ROOT: { + final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( + s, ret, parser, fromBackup); + + // Don't use addShortcut(), we don't need to save the icon. + ret.mPackages.put(shortcuts.getPackageName(), shortcuts); + continue; + } + + case ShortcutLauncher.TAG_ROOT: { + ret.addLauncher( + ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); + continue; + } } } + ShortcutService.warnForInvalidTag(depth, tag); } - ShortcutService.warnForInvalidTag(depth, tag); + } catch (RuntimeException e) { + throw new ShortcutService.InvalidFileFormatException( + "Unable to parse file", e); } return ret; } @@ -447,6 +477,51 @@ class ShortcutUser { } } + public void mergeRestoredFile(ShortcutUser restored) { + final ShortcutService s = mService; + // Note, a restore happens only at the end of setup wizard. At this point, no apps are + // installed from Play Store yet, but it's still possible that system apps have already + // published dynamic shortcuts, since some apps do so on BOOT_COMPLETED. + // When such a system app has allowbackup=true, then we go ahead and replace all existing + // shortcuts with the restored shortcuts. (Then we'll re-publish manifest shortcuts later + // in the call site.) + // When such a system app has allowbackup=false, then we'll keep the shortcuts that have + // already been published. So we selectively add restored ShortcutPackages here. + // + // The same logic applies to launchers, but since launchers shouldn't pin shortcuts + // without users interaction it's really not a big deal, so we just clear existing + // ShortcutLauncher instances in mLaunchers and add all the restored ones here. + + mLaunchers.clear(); + restored.forAllLaunchers(sl -> { + // If the app is already installed and allowbackup = false, then ignore the restored + // data. + if (s.isPackageInstalled(sl.getPackageName(), getUserId()) + && !s.shouldBackupApp(sl.getPackageName(), getUserId())) { + return; + } + addLauncher(sl); + }); + restored.forAllPackages(sp -> { + // If the app is already installed and allowbackup = false, then ignore the restored + // data. + if (s.isPackageInstalled(sp.getPackageName(), getUserId()) + && !s.shouldBackupApp(sp.getPackageName(), getUserId())) { + return; + } + + final ShortcutPackage previous = getPackageShortcutsIfExists(sp.getPackageName()); + if (previous != null && previous.hasNonManifestShortcuts()) { + Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored." + + " Existing non-manifeset shortcuts will be overwritten."); + } + addPackage(sp); + }); + // Empty the launchers and packages in restored to avoid accidentally using them. + restored.mLaunchers.clear(); + restored.mPackages.clear(); + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { pw.print(prefix); pw.print("User: "); @@ -457,6 +532,8 @@ class ShortcutUser { pw.print(mLastAppScanTime); pw.print("] "); pw.print(ShortcutService.formatTime(mLastAppScanTime)); + pw.print(" Last app scan FP: "); + pw.print(mLastAppScanOsFingerprint); pw.println(); prefix += prefix + " "; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index c9ad49a565c1..af055da2b9e6 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -180,7 +180,8 @@ public class UserManagerService extends IUserManager.Stub { UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_RESTRICTED - | UserInfo.FLAG_GUEST; + | UserInfo.FLAG_GUEST + | UserInfo.FLAG_DEMO; private static final int MIN_USER_ID = 10; // We need to keep process uid within Integer.MAX_VALUE. diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a39add80ba8f..ec07812c71f6 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5318,15 +5318,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean showing = mKeyguardDelegate.isShowing(); if (wasOccluded && !isOccluded && showing) { mKeyguardOccluded = false; - mKeyguardDelegate.setOccluded(false); + mKeyguardDelegate.setOccluded(false, true /* animate */); mStatusBar.getAttrs().privateFlags |= PRIVATE_FLAG_KEYGUARD; if (!mKeyguardDelegate.hasLockscreenWallpaper()) { mStatusBar.getAttrs().flags |= FLAG_SHOW_WALLPAPER; } + Animation anim = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.wallpaper_open_exit); + mWindowManagerFuncs.overridePlayingAppAnimationsLw(anim); return true; } else if (!wasOccluded && isOccluded && showing) { mKeyguardOccluded = true; - mKeyguardDelegate.setOccluded(true); + mKeyguardDelegate.setOccluded(true, false /* animate */); mStatusBar.getAttrs().privateFlags &= ~PRIVATE_FLAG_KEYGUARD; mStatusBar.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER; return true; @@ -6397,6 +6400,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } mWindowManagerDrawComplete = true; + if (mKeyguardDelegate != null) { + mKeyguardDelegate.onDrawCompleteLw(); + } } finishScreenTurningOn(); @@ -6866,7 +6872,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public void systemReady() { - mKeyguardDelegate = new KeyguardServiceDelegate(mContext); + mKeyguardDelegate = new KeyguardServiceDelegate(mContext, + mWindowManagerFuncs.getWindowManagerLock()); mKeyguardDelegate.onSystemReady(); readCameraLensCoverState(); diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 4fce49e16815..acf855f085c3 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -19,6 +19,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManagerPolicy.OnKeyguardExitResult; +import com.android.internal.annotations.GuardedBy; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; @@ -43,6 +44,7 @@ public class KeyguardServiceDelegate { private static final int INTERACTIVE_STATE_AWAKE = 1; private static final int INTERACTIVE_STATE_GOING_TO_SLEEP = 2; + private final Object mWindowManagerLock; protected KeyguardServiceWrapper mKeyguardService; private final Context mContext; private final View mScrim; // shown if keyguard crashes @@ -50,6 +52,9 @@ public class KeyguardServiceDelegate { private final KeyguardState mKeyguardState = new KeyguardState(); private DrawnListener mDrawnListenerWhenConnect; + @GuardedBy("mWindowManagerLock") + private boolean mHideScrimPending; + private static final class KeyguardState { KeyguardState() { // Assume keyguard is showing and secure until we know for sure. This is here in @@ -92,10 +97,12 @@ public class KeyguardServiceDelegate { @Override public void onDrawn() throws RemoteException { if (DEBUG) Log.v(TAG, "**** SHOWN CALLED ****"); + synchronized (mWindowManagerLock) { + mHideScrimPending = true; + } if (mDrawnListener != null) { mDrawnListener.onDrawn(); } - hideScrim(); } }; @@ -116,7 +123,8 @@ public class KeyguardServiceDelegate { } }; - public KeyguardServiceDelegate(Context context) { + public KeyguardServiceDelegate(Context context, Object windowManagerLock) { + mWindowManagerLock = windowManagerLock; mContext = context; mScrimHandler = UiThread.getHandler(); mScrim = createScrim(context, mScrimHandler); @@ -180,7 +188,7 @@ public class KeyguardServiceDelegate { mKeyguardService.onBootCompleted(); } if (mKeyguardState.occluded) { - mKeyguardService.setOccluded(mKeyguardState.occluded); + mKeyguardService.setOccluded(mKeyguardState.occluded, false /* animate */); } } @@ -232,10 +240,10 @@ public class KeyguardServiceDelegate { } } - public void setOccluded(boolean isOccluded) { + public void setOccluded(boolean isOccluded, boolean animate) { if (mKeyguardService != null) { - if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ")"); - mKeyguardService.setOccluded(isOccluded); + if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate); + mKeyguardService.setOccluded(isOccluded, animate); } mKeyguardState.occluded = isOccluded; } @@ -355,6 +363,16 @@ public class KeyguardServiceDelegate { } } + /** + * Called when all windows were fully drawn. + */ + public void onDrawCompleteLw() { + if (mHideScrimPending) { + hideScrim(); + mHideScrimPending = false; + } + } + private static View createScrim(Context context, Handler handler) { final View view = new View(context); diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 55652fe3a4e4..2169927d89ee 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -63,9 +63,9 @@ public class KeyguardServiceWrapper implements IKeyguardService { } @Override // Binder interface - public void setOccluded(boolean isOccluded) { + public void setOccluded(boolean isOccluded, boolean animate) { try { - mService.setOccluded(isOccluded); + mService.setOccluded(isOccluded, animate); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index d9c42541f9f4..a7b9cf44bae6 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -102,9 +102,8 @@ public class TrustManagerService extends SystemService { private static final int MSG_START_USER = 7; private static final int MSG_CLEANUP_USER = 8; private static final int MSG_SWITCH_USER = 9; - private static final int MSG_SET_DEVICE_LOCKED = 10; - private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 11; - private static final int MSG_UNLOCK_USER = 12; + private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 10; + private static final int MSG_UNLOCK_USER = 11; private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000; @@ -317,20 +316,6 @@ public class TrustManagerService extends SystemService { } } - public void setDeviceLockedForUser(int userId, boolean locked) { - if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { - synchronized (mDeviceLockedForUser) { - mDeviceLockedForUser.put(userId, locked); - } - if (locked) { - try { - ActivityManagerNative.getDefault().notifyLockedProfile(userId); - } catch (RemoteException e) { - } - } - } - } - boolean isDeviceLockedInner(int userId) { synchronized (mDeviceLockedForUser) { return mDeviceLockedForUser.get(userId, true); @@ -838,10 +823,24 @@ public class TrustManagerService extends SystemService { } @Override - public void setDeviceLockedForUser(int userId, boolean value) { + public void setDeviceLockedForUser(int userId, boolean locked) { enforceReportPermission(); - mHandler.obtainMessage(MSG_SET_DEVICE_LOCKED, value ? 1 : 0, userId) - .sendToTarget(); + final long identity = Binder.clearCallingIdentity(); + try { + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { + synchronized (mDeviceLockedForUser) { + mDeviceLockedForUser.put(userId, locked); + } + if (locked) { + try { + ActivityManagerNative.getDefault().notifyLockedProfile(userId); + } catch (RemoteException e) { + } + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } } @Override @@ -917,9 +916,6 @@ public class TrustManagerService extends SystemService { mCurrentUser = msg.arg1; refreshDeviceLockedForUser(UserHandle.USER_ALL); break; - case MSG_SET_DEVICE_LOCKED: - setDeviceLockedForUser(msg.arg2, msg.arg1 != 0); - break; case MSG_FLUSH_TRUST_USUALLY_MANAGED: SparseBooleanArray usuallyManaged; synchronized (mTrustUsuallyManagedForUser) { diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java index acd65875ffac..db7df25da3de 100644 --- a/services/core/java/com/android/server/twilight/TwilightService.java +++ b/services/core/java/com/android/server/twilight/TwilightService.java @@ -151,7 +151,7 @@ public final class TwilightService extends SystemService } private void startListening() { - if (DEBUG) Slog.d(TAG, "startListening"); + Slog.d(TAG, "startListening"); // Start listening for location updates (default: low power, max 1h, min 10m). mLocationManager.requestLocationUpdates( @@ -173,7 +173,7 @@ public final class TwilightService extends SystemService mTimeChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (DEBUG) Slog.d(TAG, "onReceive: " + intent); + Slog.d(TAG, "onReceive: " + intent); updateTwilightState(); } }; @@ -188,7 +188,7 @@ public final class TwilightService extends SystemService } private void stopListening() { - if (DEBUG) Slog.d(TAG, "stopListening"); + Slog.d(TAG, "stopListening"); if (mTimeChangedReceiver != null) { getContext().unregisterReceiver(mTimeChangedReceiver); @@ -241,15 +241,20 @@ public final class TwilightService extends SystemService @Override public void onAlarm() { - if (DEBUG) Slog.d(TAG, "onAlarm"); + Slog.d(TAG, "onAlarm"); updateTwilightState(); } @Override public void onLocationChanged(Location location) { - if (DEBUG) Slog.d(TAG, "onLocationChanged: " + location); - mLastLocation = location; - updateTwilightState(); + if (location != null) { + Slog.d(TAG, "onLocationChanged:" + + " provider=" + location.getProvider() + + " accuracy=" + location.getAccuracy() + + " time=" + location.getTime()); + mLastLocation = location; + updateTwilightState(); + } } @Override diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 621e43a12d48..a8a0b0efb784 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -30,6 +30,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.WINDOW_REPLACEMENT_TIMEOUT_DURATION; import static com.android.server.wm.WindowManagerService.H.NOTIFY_ACTIVITY_DRAWN; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; import com.android.server.input.InputApplicationHandle; import com.android.server.wm.WindowManagerService.H; @@ -44,6 +45,7 @@ import android.util.Slog; import android.view.IApplicationToken; import android.view.View; import android.view.WindowManager; +import android.view.animation.Animation; import java.io.PrintWriter; import java.util.ArrayDeque; @@ -838,6 +840,18 @@ class AppWindowToken extends WindowToken { } } + /** + * See {@link WindowManagerService#overridePlayingAppAnimationsLw} + */ + void overridePlayingAppAnimations(Animation a) { + if (mAppAnimator.isAnimating()) { + final WindowState win = findMainWindow(); + final int width = win.mContainingFrame.width(); + final int height = win.mContainingFrame.height(); + mAppAnimator.setAnimation(a, width, height, false, STACK_CLIP_NONE); + } + } + @Override void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 1d57872b2fbc..9b5b101f04fc 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -35,6 +35,7 @@ import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; +import android.view.animation.Animation; import java.io.PrintWriter; import java.util.ArrayList; @@ -637,7 +638,7 @@ class DisplayContent { */ TaskStack getDockedStackVisibleForUserLocked() { final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID); - return (stack != null && stack.isVisibleForUserLocked()) ? stack : null; + return (stack != null && stack.isVisibleLocked(true /* ignoreKeyguard */)) ? stack : null; } /** @@ -674,4 +675,13 @@ class DisplayContent { return touchedWin; } + + /** + * See {@link WindowManagerService#overridePlayingAppAnimationsLw}. + */ + void overridePlayingAppAnimationsLw(Animation a) { + for (int i = mStacks.size() - 1; i >= 0; i--) { + mStacks.get(i).overridePlayingAppAnimations(a); + } + } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 1dcada6729fc..ca1830106fb5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -38,6 +38,7 @@ import android.util.EventLog; import android.util.Slog; import android.view.DisplayInfo; import android.view.Surface; +import android.view.animation.Animation; import com.android.server.EventLogTags; @@ -677,19 +678,6 @@ class Task implements DimLayer.DimLayerUser { return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers; } - boolean isVisibleForUser() { - for (int i = mAppTokens.size() - 1; i >= 0; i--) { - final AppWindowToken appToken = mAppTokens.get(i); - for (int j = appToken.allAppWindows.size() - 1; j >= 0; j--) { - WindowState window = appToken.allAppWindows.get(j); - if (!window.isHiddenFromUserLocked()) { - return true; - } - } - } - return false; - } - boolean isVisible() { for (int i = mAppTokens.size() - 1; i >= 0; i--) { final AppWindowToken appToken = mAppTokens.get(i); @@ -778,6 +766,15 @@ class Task implements DimLayer.DimLayerUser { return mStack.getDisplayContent().getDisplayInfo(); } + /** + * See {@link WindowManagerService#overridePlayingAppAnimationsLw} + */ + void overridePlayingAppAnimations(Animation a) { + for (int i = mAppTokens.size() - 1; i >= 0; i--) { + mAppTokens.get(i).overridePlayingAppAnimations(a); + } + } + @Override public String toString() { return "{taskId=" + mTaskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}"; diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 8be5b19d1530..8f8f6422091c 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -44,6 +44,7 @@ import android.util.SparseArray; import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; +import android.view.animation.Animation; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; @@ -398,23 +399,21 @@ public class TaskStack implements DimLayer.DimLayerUser, return false; } - final int oldDockSide = mStackId == DOCKED_STACK_ID ? getDockSide() : DOCKED_INVALID; mTmpRect2.set(mBounds); mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2); if (mStackId == DOCKED_STACK_ID) { repositionDockedStackAfterRotation(mTmpRect2); snapDockedStackAfterRotation(mTmpRect2); final int newDockSide = getDockSide(mTmpRect2); - if (oldDockSide != newDockSide) { - // Update the dock create mode and clear the dock create bounds, these - // might change after a rotation and the original values will be invalid. - mService.setDockedStackCreateStateLocked( - (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP) - ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT - : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, - null); - mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide); - } + + // Update the dock create mode and clear the dock create bounds, these + // might change after a rotation and the original values will be invalid. + mService.setDockedStackCreateStateLocked( + (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP) + ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT + : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, + null); + mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide); } mBoundsAfterRotation.set(mTmpRect2); @@ -890,7 +889,7 @@ public class TaskStack implements DimLayer.DimLayerUser, mAdjustImeAmount = adjustAmount; mAdjustDividerAmount = adjustDividerAmount; updateAdjustedBounds(); - return isVisibleForUserLocked(); + return isVisibleLocked(true /* ignoreKeyguard */); } else { return false; } @@ -926,7 +925,7 @@ public class TaskStack implements DimLayer.DimLayerUser, if (minimizeAmount != mMinimizeAmount) { mMinimizeAmount = minimizeAmount; updateAdjustedBounds(); - return isVisibleForUserLocked(); + return isVisibleLocked(true /* ignoreKeyguard*/); } else { return false; } @@ -943,7 +942,7 @@ public class TaskStack implements DimLayer.DimLayerUser, void beginImeAdjustAnimation() { for (int j = mTasks.size() - 1; j >= 0; j--) { final Task task = mTasks.get(j); - if (task.isVisibleForUser()) { + if (task.isVisible()) { task.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER); task.addWindowsWaitingForDrawnIfResizingChanged(); } @@ -1233,9 +1232,13 @@ public class TaskStack implements DimLayer.DimLayerUser, } boolean isVisibleLocked() { + return isVisibleLocked(false /* ignoreKeyguard */); + } + + boolean isVisibleLocked(boolean ignoreKeyguard) { final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded() && !mService.mAnimator.mKeyguardGoingAway; - if (keyguardOn && !StackId.isAllowedOverLockscreen(mStackId)) { + if (!ignoreKeyguard && keyguardOn && !StackId.isAllowedOverLockscreen(mStackId)) { // The keyguard is showing and the stack shouldn't show on top of the keyguard. return false; } @@ -1252,20 +1255,6 @@ public class TaskStack implements DimLayer.DimLayerUser, return false; } - /** - * @return true if a the stack is visible for the current in user, ignoring any other visibility - * aspects, and false otherwise - */ - boolean isVisibleForUserLocked() { - for (int i = mTasks.size() - 1; i >= 0; i--) { - final Task task = mTasks.get(i); - if (task.isVisibleForUser()) { - return true; - } - } - return false; - } - boolean isDragResizing() { return mDragResizing; } @@ -1379,4 +1368,13 @@ public class TaskStack implements DimLayer.DimLayerUser, public boolean getBoundsAnimating() { return mBoundsAnimating; } + + /** + * See {@link WindowManagerService#overridePlayingAppAnimationsLw} + */ + void overridePlayingAppAnimations(Animation a) { + for (int i = mTasks.size() - 1; i >= 0; --i) { + mTasks.get(i).overridePlayingAppAnimations(a); + } + } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 2b66c3af2beb..e7ceba90bde3 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -757,14 +757,16 @@ class WallpaperController { } // Now stick it in. For apps over wallpaper keep the wallpaper at the bottommost - // layer. For keyguard over wallpaper put the wallpaper under the keyguard. + // layer. For keyguard over wallpaper put the wallpaper under the lowest window that + // is currently on screen, i.e. not hidden by policy. int insertionIndex = 0; if (visible && wallpaperTarget != null) { final int type = wallpaperTarget.mAttrs.type; final int privateFlags = wallpaperTarget.mAttrs.privateFlags; if ((privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 || type == TYPE_KEYGUARD_SCRIM) { - insertionIndex = windows.indexOf(wallpaperTarget); + insertionIndex = Math.min(windows.indexOf(wallpaperTarget), + findLowestWindowOnScreen(windows)); } } if (DEBUG_WALLPAPER_LIGHT || DEBUG_WINDOW_MOVEMENT @@ -781,6 +783,21 @@ class WallpaperController { return changed; } + /** + * @return The index in {@param windows} of the lowest window that is currently on screen and + * not hidden by the policy. + */ + private int findLowestWindowOnScreen(WindowList windows) { + final int size = windows.size(); + for (int index = 0; index < size; index++) { + final WindowState win = windows.get(index); + if (win.isOnScreen()) { + return index; + } + } + return Integer.MAX_VALUE; + } + boolean adjustWallpaperWindows() { mService.mWindowPlacerLocked.mWallpaperMayChange = false; diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index b0d357c6320c..47b0f3b41833 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -234,14 +234,12 @@ public class WindowAnimator { boolean allowWhenLocked = false; // Show IME over the keyguard if the target allows it allowWhenLocked |= (win.mIsImWindow || imeTarget == win) && showImeOverKeyguard; - // Show SHOW_WHEN_LOCKED windows that turn on the screen - allowWhenLocked |= (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0 && win.mTurnOnScreen; + // Show SHOW_WHEN_LOCKED windows + allowWhenLocked |= (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0; if (appShowWhenLocked != null) { allowWhenLocked |= appShowWhenLocked == win.mAppToken - // Show all SHOW_WHEN_LOCKED windows if some apps are shown over lockscreen - || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0 - // Show error dialogs over apps that dismiss keyguard. + // Show error dialogs over apps that are shown on lockscreen || (win.mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_ERROR) != 0; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index eb9ad6caee8c..47a4114d7df8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -249,6 +249,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_KEEP_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub @@ -2908,12 +2909,11 @@ public class WindowManagerService extends IWindowManager.Stub } result |= RELAYOUT_RES_SURFACE_CHANGED; } - final WindowSurfaceController surfaceController = winAnimator.mSurfaceController; - if (viewVisibility == View.VISIBLE && surfaceController != null) { + if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) { // We already told the client to go invisible, but the message may not be // handled yet, or it might want to draw a last frame. If we already have a // surface, let the client use that, but don't create new surface at this point. - surfaceController.getSurface(outSurface); + winAnimator.mSurfaceController.getSurface(outSurface); } else { if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win); @@ -5212,6 +5212,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public void overridePlayingAppAnimationsLw(Animation a) { + getDefaultDisplayContentLocked().overridePlayingAppAnimationsLw(a); + } + /** * Re-sizes a stack and its containing tasks. * @param stackId Id of stack to resize. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e828650a3049..a96cef0e40bf 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3673,12 +3673,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private boolean isActivePasswordSufficientForUserLocked( DevicePolicyData policy, int userHandle, boolean parent) { - if (policy.mActivePasswordQuality < getPasswordQuality(null, userHandle, parent) - || policy.mActivePasswordLength < getPasswordMinimumLength( + final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent); + if (policy.mActivePasswordQuality < requiredPasswordQuality) { + return false; + } + if (requiredPasswordQuality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC + && policy.mActivePasswordLength < getPasswordMinimumLength( null, userHandle, parent)) { return false; } - if (policy.mActivePasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { + if (requiredPasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { return true; } return policy.mActivePasswordUpperCase >= getPasswordMinimumUpperCase( diff --git a/services/net/Android.mk b/services/net/Android.mk index 336bc45c6ab7..408794e7d0b9 100644 --- a/services/net/Android.mk +++ b/services/net/Android.mk @@ -7,4 +7,7 @@ LOCAL_MODULE := services.net LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_AIDL_INCLUDES += \ + system/netd/server/binder + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 654ef18f9608..6d9020390bef 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -382,6 +382,7 @@ public class IpManager extends StateMachine { private final State mStoppedState = new StoppedState(); private final State mStoppingState = new StoppingState(); private final State mStartedState = new StartedState(); + private final State mRunningState = new RunningState(); private final String mTag; private final Context mContext; @@ -476,6 +477,7 @@ public class IpManager extends StateMachine { // Super simple StateMachine. addState(mStoppedState); addState(mStartedState); + addState(mRunningState, mStartedState); addState(mStoppingState); setInitialState(mStoppedState); @@ -570,7 +572,7 @@ public class IpManager extends StateMachine { pw.decreaseIndent(); pw.println(); - pw.println("StateMachine dump:"); + pw.println(mTag + " StateMachine dump:"); pw.increaseIndent(); mLocalLog.readOnlyLocalLog().dump(fd, pw, args); pw.decreaseIndent(); @@ -768,6 +770,11 @@ public class IpManager extends StateMachine { // - IPv6 addresses // - IPv6 routes // - IPv6 DNS servers + // + // N.B.: this is fundamentally race-prone and should be fixed by + // changing NetlinkTracker from a hybrid edge/level model to an + // edge-only model, or by giving IpManager its own netlink socket(s) + // so as to track all required information directly. LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); for (RouteInfo route : netlinkLinkProperties.getRoutes()) { @@ -939,16 +946,30 @@ public class IpManager extends StateMachine { return true; } + private void stopAllIP() { + // We don't need to worry about routes, just addresses, because: + // - disableIpv6() will clear autoconf IPv6 routes as well, and + // - we don't get IPv4 routes from netlink + // so we neither react to nor need to wait for changes in either. + + try { + mNwService.disableIpv6(mInterfaceName); + } catch (Exception e) { + Log.e(mTag, "Failed to disable IPv6" + e); + } + + try { + mNwService.clearInterfaceAddresses(mInterfaceName); + } catch (Exception e) { + Log.e(mTag, "Failed to clear addresses " + e); + } + } + class StoppedState extends State { @Override public void enter() { - try { - mNwService.disableIpv6(mInterfaceName); - mNwService.clearInterfaceAddresses(mInterfaceName); - } catch (Exception e) { - Log.e(mTag, "Failed to clear addresses or disable IPv6" + e); - } + stopAllIP(); resetLinkProperties(); if (mStartTimeMillis > 0) { @@ -1023,12 +1044,71 @@ public class IpManager extends StateMachine { } class StartedState extends State { - private boolean mDhcpActionInFlight; - @Override public void enter() { mStartTimeMillis = SystemClock.elapsedRealtime(); + if (mConfiguration.mProvisioningTimeoutMs > 0) { + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mProvisioningTimeoutMs; + mProvisioningTimeoutAlarm.schedule(alarmTime); + } + + if (readyToProceed()) { + transitionTo(mRunningState); + } else { + // Clear all IPv4 and IPv6 before proceeding to RunningState. + // Clean up any leftover state from an abnormal exit from + // tethering or during an IpManager restart. + stopAllIP(); + } + } + + @Override + public void exit() { + mProvisioningTimeoutAlarm.cancel(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_STOP: + transitionTo(mStoppingState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + if (readyToProceed()) { + transitionTo(mRunningState); + } + break; + + case EVENT_PROVISIONING_TIMEOUT: + handleProvisioningFailure(); + break; + + default: + // It's safe to process messages out of order because the + // only message that can both + // a) be received at this time and + // b) affect provisioning state + // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). + deferMessage(msg); + } + return HANDLED; + } + + boolean readyToProceed() { + return (!mLinkProperties.hasIPv4Address() && + !mLinkProperties.hasGlobalIPv6Address()); + } + } + + class RunningState extends State { + private boolean mDhcpActionInFlight; + + @Override + public void enter() { mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface, mCallback, mMulticastFiltering); // TODO: investigate the effects of any multicast filtering racing/interfering with the @@ -1037,12 +1117,6 @@ public class IpManager extends StateMachine { mCallback.setFallbackMulticastFilter(mMulticastFiltering); } - if (mConfiguration.mProvisioningTimeoutMs > 0) { - final long alarmTime = SystemClock.elapsedRealtime() + - mConfiguration.mProvisioningTimeoutMs; - mProvisioningTimeoutAlarm.schedule(alarmTime); - } - if (mConfiguration.mEnableIPv6) { // TODO: Consider transitionTo(mStoppingState) if this fails. startIPv6(); @@ -1070,7 +1144,6 @@ public class IpManager extends StateMachine { @Override public void exit() { - mProvisioningTimeoutAlarm.cancel(); stopDhcpAction(); if (mIpReachabilityMonitor != null) { @@ -1167,10 +1240,6 @@ public class IpManager extends StateMachine { break; } - case EVENT_PROVISIONING_TIMEOUT: - handleProvisioningFailure(); - break; - case EVENT_DHCPACTION_TIMEOUT: stopDhcpAction(); break; diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/util/NetdService.java new file mode 100644 index 000000000000..153cb502afca --- /dev/null +++ b/services/net/java/android/net/util/NetdService.java @@ -0,0 +1,46 @@ +/* + * 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.net.util; + +import android.net.INetd; +import android.os.ServiceManager; +import android.util.Log; + + +/** + * @hide + */ +public class NetdService { + private static final String TAG = NetdService.class.getSimpleName(); + private static final String NETD_SERVICE_NAME = "netd"; + + /** + * It is the caller's responsibility to check for a null return value + * and to handle RemoteException errors from invocations on the returned + * interface if, for example, netd dies and is restarted. + * + * @return an INetd instance or null. + */ + public static INetd getInstance() { + final INetd netdInstance = INetd.Stub.asInterface( + ServiceManager.getService(NETD_SERVICE_NAME)); + if (netdInstance == null) { + Log.w(TAG, "WARNING: returning null INetd instance."); + } + return netdInstance; + } +} diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java index 07cc9c05f57a..07b26e83e934 100644 --- a/services/print/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java @@ -57,6 +57,9 @@ import java.util.concurrent.TimeoutException; * spooler if needed, to make the timed remote calls, to handle * remote exceptions, and to bind/unbind to the remote instance as * needed. + * + * The calls might be blocking and need the main thread of to be unblocked to finish. Hence do not + * call this while holding any monitors that might need to be acquired the main thread. */ final class RemotePrintSpooler { diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java index 05301c1cb5f2..a91cdb38a15a 100644 --- a/services/print/java/com/android/server/print/UserState.java +++ b/services/print/java/com/android/server/print/UserState.java @@ -434,12 +434,12 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, } public void createPrinterDiscoverySession(@NonNull IPrinterDiscoveryObserver observer) { + mSpooler.clearCustomPrinterIconCache(); + synchronized (mLock) { throwIfDestroyedLocked(); if (mPrinterDiscoverySession == null) { - mSpooler.clearCustomPrinterIconCache(); - // If we do not have a session, tell all service to create one. mPrinterDiscoverySession = new PrinterDiscoverySessionMediator(mContext) { @Override @@ -731,6 +731,8 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, @Override public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) { + mSpooler.onCustomPrinterIconLoaded(printerId, icon); + synchronized (mLock) { throwIfDestroyedLocked(); @@ -738,7 +740,6 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, if (mPrinterDiscoverySession == null) { return; } - mSpooler.onCustomPrinterIconLoaded(printerId, icon); mPrinterDiscoverySession.onCustomPrinterIconLoadedLocked(printerId); } } @@ -979,18 +980,21 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks, * Prune persistent state if a print service was uninstalled */ public void prunePrintServices() { + ArrayList<ComponentName> installedComponents; + synchronized (mLock) { - ArrayList<ComponentName> installedComponents = getInstalledComponents(); + installedComponents = getInstalledComponents(); // Remove unnecessary entries from persistent state "disabled services" boolean disabledServicesUninstalled = mDisabledServices.retainAll(installedComponents); if (disabledServicesUninstalled) { writeDisabledPrintServicesLocked(mDisabledServices); } - - // Remove unnecessary entries from persistent state "approved services" - mSpooler.pruneApprovedPrintServices(installedComponents); } + + // Remove unnecessary entries from persistent state "approved services" + mSpooler.pruneApprovedPrintServices(installedComponents); + } private void onConfigurationChangedLocked() { diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 404c14271b89..a3d0afab88eb 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.accounts.Account; +import android.accounts.AccountManagerInternal; import android.accounts.AuthenticatorDescription; import android.app.AppOpsManager; import android.app.Notification; @@ -44,6 +45,8 @@ import android.test.mock.MockContext; import android.test.mock.MockPackageManager; import android.util.Log; +import com.android.server.LocalServices; + import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -73,6 +76,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { SQLiteDatabase.deleteDatabase(new File(mAms.getCeDatabaseName(UserHandle.USER_SYSTEM))); SQLiteDatabase.deleteDatabase(new File(mAms.getDeDatabaseName(UserHandle.USER_SYSTEM))); SQLiteDatabase.deleteDatabase(new File(mAms.getPreNDatabaseName(UserHandle.USER_SYSTEM))); + LocalServices.removeServiceForTest(AccountManagerInternal.class); super.tearDown(); } @@ -282,6 +286,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { private AccountManagerService createAccountManagerService(Context mockContext, Context realContext) { + LocalServices.removeServiceForTest(AccountManagerInternal.class); return new MyAccountManagerService(mockContext, new MyMockPackageManager(), new MockAccountAuthenticatorCache(), realContext); } @@ -420,6 +425,11 @@ public class AccountManagerServiceTest extends AndroidTestCase { public int checkSignatures(final int uid1, final int uid2) { return PackageManager.SIGNATURE_MATCH; } + + @Override + public void addOnPermissionsChangeListener( + OnPermissionsChangedListener listener) { + } } static public class MyAccountManagerService extends AccountManagerService { diff --git a/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java b/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java index e440a0d603d2..984a484dadc2 100644 --- a/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java +++ b/services/tests/servicestests/src/com/android/server/am/TaskPersisterTest.java @@ -62,7 +62,7 @@ public class TaskPersisterTest extends AndroidTestCase { for (int i = 0; i < 100; i++) { taskIdsOnFile.put(getRandomTaskIdForUser(testUserId), true); } - mTaskPersister.maybeWritePersistedTaskIdsForUser(taskIdsOnFile, testUserId); + mTaskPersister.writePersistedTaskIdsForUser(taskIdsOnFile, testUserId); SparseBooleanArray newTaskIdsOnFile = mTaskPersister .loadPersistedTaskIdsForUser(testUserId); assertTrue("TaskIds written differ from TaskIds read back from file", diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 1c7a138468cf..792f3001c049 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -390,6 +390,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + void injectRunOnNewThread(Runnable r) { + runOnHandler(r); + } + + @Override void injectEnforceCallingPermission(String permission, String message) { if (!mCallerPermissions.contains(permission)) { throw new SecurityException("Missing permission: " + permission); @@ -402,6 +407,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + String injectBuildFingerprint() { + return mInjectedBuildFingerprint; + } + + @Override void wtf(String message, Throwable th) { // During tests, WTF is fatal. fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); @@ -523,6 +533,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected Map<String, PackageInfo> mInjectedPackages; protected Set<PackageWithUser> mUninstalledPackages; + protected Set<String> mSystemPackages; protected PackageManager mMockPackageManager; protected PackageManagerInternal mMockPackageManagerInternal; @@ -623,6 +634,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected static final String PACKAGE_FALLBACK_LAUNCHER_NAME = "fallback"; protected static final int PACKAGE_FALLBACK_LAUNCHER_PRIORITY = -999; + protected String mInjectedBuildFingerprint = "build1"; + static { QUERY_ALL.setQueryFlags( ShortcutQuery.FLAG_GET_ALL_KINDS); @@ -672,6 +685,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); mUninstalledPackages = new HashSet<>(); + mSystemPackages = new HashSet<>(); mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files"); @@ -921,6 +935,12 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { }); } + protected void setPackageLastUpdateTime(String packageName, long value) { + updatePackageInfo(packageName, pi -> { + pi.lastUpdateTime = value; + }); + } + protected void uninstallPackage(int userId, String packageName) { if (ENABLE_DUMP) { Log.v(TAG, "Unnstall package " + packageName + " / " + userId); @@ -952,6 +972,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) { ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } + if (mSystemPackages.contains(packageName)) { + ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; + } if (getSignatures) { ret.signatures = pi.signatures; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 253334eec9cf..143398f63860 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -3945,11 +3945,11 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mInjectedPackages.remove(CALLING_PACKAGE_1); mInjectedPackages.remove(CALLING_PACKAGE_3); - mService.handleUnlockUser(USER_0); + mService.checkPackageChanges(USER_0); assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0)); assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0)); - assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0)); + assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0)); // --------------- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10)); assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10)); assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); @@ -3961,7 +3961,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10)); assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10)); - mService.handleUnlockUser(USER_10); + mService.checkPackageChanges(USER_10); assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0)); assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0)); @@ -4154,7 +4154,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { updatePackageVersion(CALLING_PACKAGE_1, 1); // Then send the broadcast, to only user-0. - mService.mPackageMonitor.onReceive(getTestContext(), + mService.mPackageMonitor.onReceive(getTestContext(), genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0)); waitOnMainThread(); @@ -4186,10 +4186,13 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mInjectedCurrentTimeMillis = START_TIME + 200; mRunningUsers.put(USER_10, true); + mUnlockedUsers.put(USER_10, true); reset(c0); reset(c10); + setPackageLastUpdateTime(CALLING_PACKAGE_1, mInjectedCurrentTimeMillis); mService.handleUnlockUser(USER_10); + mService.checkPackageChanges(USER_10); waitOnMainThread(); @@ -4221,7 +4224,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // Then send the broadcast, to only user-0. mService.mPackageMonitor.onReceive(getTestContext(), genPackageUpdateIntent(CALLING_PACKAGE_2, USER_0)); - mService.handleUnlockUser(USER_10); + mService.checkPackageChanges(USER_10); waitOnMainThread(); @@ -4243,9 +4246,9 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { updatePackageVersion(CALLING_PACKAGE_3, 100); // Then send the broadcast, to only user-0. - mService.mPackageMonitor.onReceive(getTestContext(), + mService.mPackageMonitor.onReceive(getTestContext(), genPackageUpdateIntent(CALLING_PACKAGE_3, USER_0)); - mService.handleUnlockUser(USER_10); + mService.checkPackageChanges(USER_10); waitOnMainThread(); @@ -4344,6 +4347,128 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } + public void testHandlePackageUpdate_systemAppUpdate() { + + // Package1 is a system app. Package 2 is not a system app, so it's not scanned + // in this test at all. + mSystemPackages.add(CALLING_PACKAGE_1); + + // Initial state: no shortcuts. + mService.checkPackageChanges(USER_0); + + assertEquals(mInjectedCurrentTimeMillis, + mService.getUserShortcutsLocked(USER_0).getLastAppScanTime()); + assertEquals(mInjectedBuildFingerprint, + mService.getUserShortcutsLocked(USER_0).getLastAppScanOsFingerprint()); + + // They have no shortcuts. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + // Next. + // Update the packages -- now they have 1 manifest shortcut. + // But checkPackageChanges() don't notice it, since their version code / timestamp haven't + // changed. + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_1); + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()), + R.xml.shortcut_1); + mInjectedCurrentTimeMillis += 1000; + mService.checkPackageChanges(USER_0); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + // Next. + // Update the build finger print. All system apps will be scanned now. + mInjectedBuildFingerprint = "update1"; + mInjectedCurrentTimeMillis += 1000; + mService.checkPackageChanges(USER_0); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1"); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + // Next. + // Update manifest shortcuts. + mInjectedBuildFingerprint = "update2"; + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), + R.xml.shortcut_2); + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()), + R.xml.shortcut_2); + mInjectedCurrentTimeMillis += 1000; + mService.checkPackageChanges(USER_0); + + // Fingerprint hasn't changed, so CALLING_PACKAGE_1 wasn't scanned. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1"); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + // Update the fingerprint, but CALLING_PACKAGE_1's version code hasn't changed, so + // still not scanned. + mInjectedBuildFingerprint = "update2"; + mInjectedCurrentTimeMillis += 1000; + mService.checkPackageChanges(USER_0); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1"); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + // Now update the version code, so CALLING_PACKAGE_1 is scanned again. + mInjectedBuildFingerprint = "update3"; + mInjectedCurrentTimeMillis += 1000; + updatePackageVersion(CALLING_PACKAGE_1, 1); + mService.checkPackageChanges(USER_0); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("ms1", "ms2"); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertWith(getCallerShortcuts()) + .isEmpty(); + }); + + // Make sure getLastAppScanTime / getLastAppScanOsFingerprint are persisted. + initService(); + assertEquals(mInjectedCurrentTimeMillis, + mService.getUserShortcutsLocked(USER_0).getLastAppScanTime()); + assertEquals(mInjectedBuildFingerprint, + mService.getUserShortcutsLocked(USER_0).getLastAppScanOsFingerprint()); + } + public void testHandlePackageChanged() { final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1"); final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2"); @@ -5234,6 +5359,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * It's the case with preintalled apps -- when applyRestore() is called, the system * apps are already installed, so manifest shortcuts need to be re-published. + * + * Also, when a restore target app is already installed, and + * - if it has allowBackup=true, we'll restore normally, so all existing shortcuts will be + * replaced. (but manifest shortcuts will be re-published anyway.) We log a warning on + * logcat. + * - if it has allowBackup=false, we don't touch any of the existing shortcuts. */ public void testBackupAndRestore_appAlreadyInstalledWhenRestored() { // Pre-backup. Same as testBackupAndRestore_manifestRePublished(). @@ -5265,6 +5396,19 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mService.mPackageMonitor.onReceive(mServiceContext, genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); + // Set up shortcuts for package 3, which won't be backed up / restored. + addManifestShortcutResource( + new ComponentName(CALLING_PACKAGE_3, ShortcutActivity.class.getName()), + R.xml.shortcut_1); + updatePackageVersion(CALLING_PACKAGE_3, 1); + mService.mPackageMonitor.onReceive(mServiceContext, + genPackageAddIntent(CALLING_PACKAGE_3, USER_0)); + + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertTrue(getManager().setDynamicShortcuts(list( + makeShortcut("s1")))); + }); + // Make sure the manifest shortcuts have been published. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertWith(getCallerShortcuts()) @@ -5290,6 +5434,11 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .areAllDisabled(); }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1", "ms1"); + }); + // Backup and *without restarting the service, just call applyRestore()*. { int prevUid = mInjectedCallingUid; @@ -5329,6 +5478,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .areAllNotDynamic() ; }); + + // Package 3 still has the same shortcuts. + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("s1", "ms1"); + }); } public void testSaveAndLoad_crossProfile() { diff --git a/services/tests/shortcutmanagerutils/Android.mk b/services/tests/shortcutmanagerutils/Android.mk index 701e05857142..2818457c9ac9 100644 --- a/services/tests/shortcutmanagerutils/Android.mk +++ b/services/tests/shortcutmanagerutils/Android.mk @@ -26,6 +26,6 @@ LOCAL_MODULE_TAGS := optional LOCAL_MODULE := ShortcutManagerTestUtils -LOCAL_SDK_VERSION := current +LOCAL_SDK_VERSION := test_current include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 78f95c4d217e..1fe5cb782315 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -156,10 +156,10 @@ public class ShortcutManagerTestUtils { return result; } - private static List<String> runCommand(Instrumentation instrumentation, String command) { + public static List<String> runCommand(Instrumentation instrumentation, String command) { return runCommand(instrumentation, command, null); } - private static List<String> runCommand(Instrumentation instrumentation, String command, + public static List<String> runCommand(Instrumentation instrumentation, String command, Predicate<List<String>> resultAsserter) { Log.d(TAG, "Running command: " + command); final List<String> result; @@ -175,11 +175,11 @@ public class ShortcutManagerTestUtils { return result; } - private static void runCommandForNoOutput(Instrumentation instrumentation, String command) { + public static void runCommandForNoOutput(Instrumentation instrumentation, String command) { runCommand(instrumentation, command, result -> result.size() == 0); } - private static List<String> runShortcutCommand(Instrumentation instrumentation, String command, + public static List<String> runShortcutCommand(Instrumentation instrumentation, String command, Predicate<List<String>> resultAsserter) { return runCommand(instrumentation, "cmd shortcut " + command, resultAsserter); } @@ -204,7 +204,8 @@ public class ShortcutManagerTestUtils { } public static void setDefaultLauncher(Instrumentation instrumentation, String component) { - runCommand(instrumentation, "cmd package set-home-activity " + component, + runCommand(instrumentation, "cmd package set-home-activity --user " + + instrumentation.getContext().getUserId() + " " + component, result -> result.contains("Success")); } @@ -1053,7 +1054,11 @@ public class ShortcutManagerTestUtils { } public static void retryUntil(BooleanSupplier checker, String message) { - final long timeOut = System.currentTimeMillis() + 30 * 1000; // wait for 30 seconds. + retryUntil(checker, message, 30); + } + + public static void retryUntil(BooleanSupplier checker, String message, long timeoutSeconds) { + final long timeOut = System.currentTimeMillis() + timeoutSeconds * 1000; while (!checker.getAsBoolean()) { if (System.currentTimeMillis() > timeOut) { break; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 43d2a1f1156c..a04034e3f764 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -535,6 +535,18 @@ public class VoiceInteractionManagerService extends SystemService { + " user=" + userHandle); } + ComponentName getCurAssistant(int userHandle) { + String curAssistant = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ASSISTANT, userHandle); + if (TextUtils.isEmpty(curAssistant)) { + return null; + } + if (DEBUG) Slog.d(TAG, "getCurAssistant curAssistant=" + curAssistant + + " user=" + userHandle); + return ComponentName.unflattenFromString(curAssistant); + } + void resetCurAssistant(int userHandle) { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT, null, userHandle); @@ -1178,6 +1190,7 @@ public class VoiceInteractionManagerService extends SystemService { synchronized (VoiceInteractionManagerServiceStub.this) { ComponentName curInteractor = getCurInteractor(userHandle); ComponentName curRecognizer = getCurRecognizer(userHandle); + ComponentName curAssistant = getCurAssistant(userHandle); if (curRecognizer == null) { // Could a new recognizer appear when we don't have one pre-installed? if (anyPackagesAppearing()) { @@ -1196,6 +1209,7 @@ public class VoiceInteractionManagerService extends SystemService { // the default config. setCurInteractor(null, userHandle); setCurRecognizer(null, userHandle); + resetCurAssistant(userHandle); initForUser(userHandle); return; } @@ -1212,6 +1226,20 @@ public class VoiceInteractionManagerService extends SystemService { return; } + if (curAssistant != null) { + int change = isPackageDisappearing(curAssistant.getPackageName()); + if (change == PACKAGE_PERMANENT_CHANGE) { + // If the currently set assistant is being removed, then we should + // reset back to the default state (which is probably that we prefer + // to have the default full voice interactor enabled). + setCurInteractor(null, userHandle); + setCurRecognizer(null, userHandle); + resetCurAssistant(userHandle); + initForUser(userHandle); + return; + } + } + // There is no interactor, so just deal with a simple recognizer. int change = isPackageDisappearing(curRecognizer.getPackageName()); if (change == PACKAGE_PERMANENT_CHANGE diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index dc9767caf01e..d6a2ee3c2400 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -870,6 +870,11 @@ public class CarrierConfigManager { /** * Flag indicating whether the carrier supports the Hold command while in an IMS call. + * <p> + * The device configuration value {@code config_device_respects_hold_carrier_config} ultimately + * controls whether this carrier configuration option is used. Where + * {@code config_device_respects_hold_carrier_config} is false, the value of the + * {@link #KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} carrier configuration option is ignored. * @hide */ public static final String KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL = "allow_hold_in_ims_call"; diff --git a/telephony/java/android/telephony/Rlog.java b/telephony/java/android/telephony/Rlog.java index b4f400fe852c..cd0a012d14fd 100644 --- a/telephony/java/android/telephony/Rlog.java +++ b/telephony/java/android/telephony/Rlog.java @@ -16,8 +16,15 @@ package android.telephony; +import android.text.TextUtils; import android.util.Log; +import android.util.Base64; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + + /** * A class to log strings to the RADIO LOG. * @@ -87,11 +94,52 @@ public final class Rlog { /** * Redact personally identifiable information for production users. - * If log tag is loggable in verbose mode, return the original string, otherwise return XXX. + * @param tag used to identify the source of a log message + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If tag is loggable in verbose mode or pii is null, return the original input. + * otherwise return a secure Hash of input pii */ public static String pii(String tag, Object pii) { - return (isLoggable(tag, Log.VERBOSE) ? String.valueOf(pii) : "XXX"); + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || isLoggable(tag, Log.VERBOSE)) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; } + /** + * Redact personally identifiable information for production users. + * @param enablePiiLogging set when caller explicitly want to enable sensitive logging. + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If enablePiiLogging is set to true or pii is null, return the original input. + * otherwise return a secure Hash of input pii + */ + public static String pii(boolean enablePiiLogging, Object pii) { + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || enablePiiLogging) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; + } + + /** + * Returns a secure hash (using the SHA1 algorithm) of the provided input. + * + * @return the hash + * @param input the bytes for which the secure hash should be computed. + */ + private static String secureHash(byte[] input) { + MessageDigest messageDigest; + + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return "####"; + } + + byte[] result = messageDigest.digest(input); + return Base64.encodeToString( + result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } } diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 6229ed921bcc..cf2d27e28709 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -340,7 +340,7 @@ public class SubscriptionInfo implements Parcelable { String iccIdToPrint = null; if (iccId != null) { if (iccId.length() > 9 && !Build.IS_DEBUGGABLE) { - iccIdToPrint = iccId.substring(0, 9) + "XXXXXXXXXXX"; + iccIdToPrint = iccId.substring(0, 9) + Rlog.pii(false, iccId.substring(9)); } else { iccIdToPrint = iccId; } diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py index 2956d87f247c..7ec46a3ee86b 100755 --- a/tools/fonts/fontchain_lint.py +++ b/tools/fonts/fontchain_lint.py @@ -256,8 +256,8 @@ def parse_fonts_xml(fonts_xml_path): def check_emoji_coverage(all_emoji, equivalent_emoji): - emoji_font = get_emoji_font() - check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji) + emoji_font = get_emoji_font() + check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji) def get_emoji_font(): @@ -274,15 +274,12 @@ def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji): assert sequence in coverage, ( '%s is not supported in the emoji font.' % printable(sequence)) - # disable temporarily - we cover more than this - """ for sequence in coverage: if sequence in {0x0000, 0x000D, 0x0020}: # The font needs to support a few extra characters, which is OK continue assert sequence in all_emoji, ( 'Emoji font should not support %s.' % printable(sequence)) - """ for first, second in sorted(equivalent_emoji.items()): assert coverage[first] == coverage[second], ( @@ -290,8 +287,6 @@ def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji): printable(first), printable(second))) - # disable temporarily - some equivalent sequences we don't even know about - """ for glyph in set(coverage.values()): maps_to_glyph = [seq for seq in coverage if coverage[seq] == glyph] if len(maps_to_glyph) > 1: @@ -307,7 +302,7 @@ def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji): 'The sequences %s should not result in the same glyph %s' % ( printable(equivalent_seqs), glyph)) - """ + def check_emoji_defaults(default_emoji): missing_text_chars = _emoji_properties['Emoji'] - default_emoji @@ -334,15 +329,9 @@ def check_emoji_defaults(default_emoji): # Noto does not have monochrome glyphs for Unicode 7.0 wingdings and # webdings yet. missing_text_chars -= _chars_by_age['7.0'] - # TODO: Remove these after b/26113320 is fixed - missing_text_chars -= { - 0x263A, # WHITE SMILING FACE - 0x270C, # VICTORY HAND - 0x2744, # SNOWFLAKE - 0x2764, # HEAVY BLACK HEART - } assert missing_text_chars == set(), ( - 'Text style version of some emoji characters are missing: ' + repr(missing_text_chars)) + 'Text style version of some emoji characters are missing: ' + + repr(missing_text_chars)) # Setting reverse to true returns a dictionary that maps the values to sets of @@ -362,7 +351,7 @@ def parse_unicode_datafile(file_path, reverse=False): if not line: continue - chars, prop = line.split(';') + chars, prop = line.split(';')[:2] chars = chars.strip() prop = prop.strip() @@ -423,26 +412,6 @@ def parse_ucd(ucd_path): _emoji_zwj_sequences = parse_unicode_datafile( path.join(ucd_path, 'emoji-zwj-sequences.txt')) - # filter modern pentathlon, as it seems likely to be removed from final spec - # also filter rifle - def is_excluded(n): - return n in [0x1f93b, 0x1f946] - - def contains_excluded(t): - if type(t) == int: - return is_excluded(t) - return any(is_excluded(cp) for cp in t) - - # filter modern pentathlon, as it seems likely to be removed from final spec - _emoji_properties['Emoji'] = set( - t for t in _emoji_properties['Emoji'] if not contains_excluded(t)) - _emoji_sequences = dict( - (t, v) for (t, v) in _emoji_sequences.items() if not contains_excluded(t)) - - # add in UN flag - UN_seq = flag_sequence('UN') - _emoji_sequences[UN_seq] = 'Emoji_Flag_Sequence' - def flag_sequence(territory_code): return tuple(0x1F1E6 + ord(ch) - ord('A') for ch in territory_code) @@ -454,7 +423,8 @@ UNSUPPORTED_FLAGS = frozenset({ flag_sequence('GF'), flag_sequence('GP'), flag_sequence('GS'), flag_sequence('MF'), flag_sequence('MQ'), flag_sequence('NC'), flag_sequence('PM'), flag_sequence('RE'), flag_sequence('TF'), - flag_sequence('WF'), flag_sequence('XK'), flag_sequence('YT'), + flag_sequence('UN'), flag_sequence('WF'), flag_sequence('XK'), + flag_sequence('YT'), }) EQUIVALENT_FLAGS = { @@ -467,6 +437,22 @@ EQUIVALENT_FLAGS = { COMBINING_KEYCAP = 0x20E3 +# Characters that Android defaults to emoji style, different from the recommendations in UTR #51 +ANDROID_DEFAULT_EMOJI = frozenset({ + 0x2600, # BLACK SUN WITH RAYS + 0x2601, # CLOUD + 0x260E, # BLACK TELEPHONE + 0x261D, # WHITE UP POINTING INDEX + 0x263A, # WHITE SMILING FACE + 0x2660, # BLACK SPADE SUIT + 0x2663, # BLACK CLUB SUIT + 0x2665, # BLACK HEART SUIT + 0x2666, # BLACK DIAMOND SUIT + 0x270C, # VICTORY HAND + 0x2744, # SNOWFLAKE + 0x2764, # HEAVY BLACK HEART +}) + LEGACY_ANDROID_EMOJI = { 0xFE4E5: flag_sequence('JP'), 0xFE4E6: flag_sequence('US'), @@ -502,7 +488,17 @@ ZWJ_IDENTICALS = { def is_fitzpatrick_modifier(cp): - return 0x1f3fb <= cp <= 0x1f3ff + return 0x1F3FB <= cp <= 0x1F3FF + + +def reverse_emoji(seq): + rev = list(reversed(seq)) + # if there are fitzpatrick modifiers in the sequence, keep them after + # the emoji they modify + for i in xrange(1, len(rev)): + if is_fitzpatrick_modifier(rev[i-1]): + rev[i], rev[i-1] = rev[i-1], rev[i] + return tuple(rev) def compute_expected_emoji(): @@ -511,26 +507,52 @@ def compute_expected_emoji(): all_sequences = set() all_sequences.update(_emoji_variation_sequences) + # add zwj sequences not in the current emoji-zwj-sequences.txt + adjusted_emoji_zwj_sequences = dict(_emoji_zwj_sequences) + adjusted_emoji_zwj_sequences.update(_emoji_zwj_sequences) + # single parent families + additional_emoji_zwj = ( + (0x1F468, 0x200D, 0x1F466), + (0x1F468, 0x200D, 0x1F467), + (0x1F468, 0x200D, 0x1F466, 0x200D, 0x1F466), + (0x1F468, 0x200D, 0x1F467, 0x200D, 0x1F466), + (0x1F468, 0x200D, 0x1F467, 0x200D, 0x1F467), + (0x1F469, 0x200D, 0x1F466), + (0x1F469, 0x200D, 0x1F467), + (0x1F469, 0x200D, 0x1F466, 0x200D, 0x1F466), + (0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F466), + (0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F467), + ) + # sequences formed from man and woman and optional fitzpatrick modifier + modified_extensions = ( + 0x2696, + 0x2708, + 0x1F3A8, + 0x1F680, + 0x1F692, + ) + for seq in additional_emoji_zwj: + adjusted_emoji_zwj_sequences[seq] = 'Emoji_ZWJ_Sequence' + for ext in modified_extensions: + for base in (0x1F468, 0x1F469): + seq = (base, 0x200D, ext) + adjusted_emoji_zwj_sequences[seq] = 'Emoji_ZWJ_Sequence' + for modifier in range(0x1F3FB, 0x1F400): + seq = (base, modifier, 0x200D, ext) + adjusted_emoji_zwj_sequences[seq] = 'Emoji_ZWJ_Sequence' + for sequence in _emoji_sequences.keys(): sequence = tuple(ch for ch in sequence if ch != EMOJI_VS) all_sequences.add(sequence) sequence_pieces.update(sequence) - for sequence in _emoji_zwj_sequences.keys(): + for sequence in adjusted_emoji_zwj_sequences.keys(): sequence = tuple(ch for ch in sequence if ch != EMOJI_VS) all_sequences.add(sequence) sequence_pieces.update(sequence) # Add reverse of all emoji ZWJ sequences, which are added to the fonts # as a workaround to get the sequences work in RTL text. - reversed_seq = list(reversed(sequence)) - # if there are fitzpatrick modifiers in the sequence, keep them after - # the emoji they modify - for i in xrange(1, len(reversed_seq)): - if is_fitzpatrick_modifier(reversed_seq[i - 1]): - tmp = reversed_seq[i] - reversed_seq[i] = reversed_seq[i-1] - reversed_seq[i-1] = tmp - reversed_seq = tuple(reversed_seq) + reversed_seq = reverse_emoji(sequence) all_sequences.add(reversed_seq) equivalent_emoji[reversed_seq] = sequence @@ -549,6 +571,7 @@ def compute_expected_emoji(): set(LEGACY_ANDROID_EMOJI.keys())) default_emoji = ( _emoji_properties['Emoji_Presentation'] | + ANDROID_DEFAULT_EMOJI | all_sequences | set(LEGACY_ANDROID_EMOJI.keys())) diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 9d0c20ce4c5d..d3d5ea05c1af 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -817,6 +817,7 @@ public class WifiConfiguration implements Parcelable { */ public static final int NETWORK_SELECTION_ENABLE = 0; /** + * @deprecated it is not used any more. * This network is disabled because higher layer (>2) network is bad */ public static final int DISABLED_BAD_LINK = 1; @@ -862,7 +863,7 @@ public class WifiConfiguration implements Parcelable { */ private static final String[] QUALITY_NETWORK_SELECTION_DISABLE_REASON = { "NETWORK_SELECTION_ENABLE", - "NETWORK_SELECTION_DISABLED_BAD_LINK", + "NETWORK_SELECTION_DISABLED_BAD_LINK", // deprecated "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ", "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE", "NETWORK_SELECTION_DISABLED_DHCP_FAILURE", diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index bbc3d2fd5927..1633bd9c914a 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -49,6 +49,8 @@ import java.util.concurrent.CountDownLatch; * This class provides the primary API for managing all aspects of Wi-Fi * connectivity. Get an instance of this class by calling * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context.WIFI_SERVICE)}. + * On releases before NYC, it should only be obtained from an application context, and not from + * any other derived context to avoid memory leaks within the calling process. * It deals with several categories of items: * <ul> |