diff options
116 files changed, 2341 insertions, 779 deletions
diff --git a/CleanSpec.mk b/CleanSpec.mk index ae2f3ad..c7cf940 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -218,6 +218,9 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/androi $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/services.core_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/services_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/services.core_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/services_intermediates) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER diff --git a/api/current.txt b/api/current.txt index 584246b..c0bfd83 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4490,6 +4490,7 @@ package android.app { public class KeyguardManager { method public deprecated void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult); + method public android.content.Intent getConfirmDeviceCredentialIntent(java.lang.CharSequence, java.lang.CharSequence); method public boolean inKeyguardRestrictedInputMode(); method public boolean isKeyguardLocked(); method public boolean isKeyguardSecure(); @@ -7680,7 +7681,6 @@ package android.content { field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER"; field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS"; field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED"; - field public static final java.lang.String ACTION_CONFIRM_DEVICE_CREDENTIAL = "android.intent.action.CONFIRM_DEVICE_CREDENTIAL"; field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; field public static final java.lang.String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT"; field public static final java.lang.String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED"; @@ -7824,7 +7824,6 @@ package android.content { field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list"; field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list"; field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; - field public static final java.lang.String EXTRA_DETAILS = "android.intent.extra.DETAILS"; field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE"; field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2 field public static final int EXTRA_DOCK_STATE_DESK = 1; // 0x1 @@ -11655,7 +11654,7 @@ package android.graphics.drawable { public class AnimatedStateListDrawable extends android.graphics.drawable.StateListDrawable { ctor public AnimatedStateListDrawable(); method public void addState(int[], android.graphics.drawable.Drawable, int); - method public void addTransition(int, int, android.graphics.drawable.Drawable, boolean); + method public void addTransition(int, int, T, boolean); } public class AnimatedVectorDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable { @@ -23027,9 +23026,9 @@ package android.os { method public boolean isUserRunning(android.os.UserHandle); method public boolean isUserRunningOrStopping(android.os.UserHandle); method public boolean setRestrictionsChallenge(java.lang.String); - method public void setUserRestriction(java.lang.String, boolean); - method public void setUserRestrictions(android.os.Bundle); - method public void setUserRestrictions(android.os.Bundle, android.os.UserHandle); + method public deprecated void setUserRestriction(java.lang.String, boolean); + method public deprecated void setUserRestrictions(android.os.Bundle); + method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle); field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user"; field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps"; diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java index b13b009..b37f896 100644 --- a/cmds/media/src/com/android/commands/media/Media.java +++ b/cmds/media/src/com/android/commands/media/Media.java @@ -225,7 +225,7 @@ public class Media extends BaseCommand { void printUsageMessage() { try { System.out.println("V2Monitoring session " + mController.getTag() - + "... available commands:"); + + "... available commands: play, pause, next, previous"); } catch (RemoteException e) { System.out.println("Error trying to monitor session!"); } @@ -257,6 +257,14 @@ public class Media extends BaseCommand { addNewline = false; } else if ("q".equals(line) || "quit".equals(line)) { break; + } else if ("play".equals(line)) { + mController.play(); + } else if ("pause".equals(line)) { + mController.pause(); + } else if ("next".equals(line)) { + mController.next(); + } else if ("previous".equals(line)) { + mController.previous(); } else { System.out.println("Invalid command: " + line); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1b82d8e..d9ea671 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1807,9 +1807,13 @@ public final class ActivityThread { } } - public void installSystemApplicationInfo(ApplicationInfo info) { + public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { synchronized (this) { - getSystemContext().installSystemApplicationInfo(info); + getSystemContext().installSystemApplicationInfo(info, classLoader); + + // The code package for "android" in the system server needs + // to be the system context's package. + mPackages.put("android", new WeakReference<LoadedApk>(getSystemContext().mPackageInfo)); // give ourselves a default profiler mProfiler = new Profiler(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4cf8cb4..da343ac 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2301,8 +2301,8 @@ class ContextImpl extends Context { } } - void installSystemApplicationInfo(ApplicationInfo info) { - mPackageInfo.installSystemApplicationInfo(info); + void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { + mPackageInfo.installSystemApplicationInfo(info, classLoader); } final void scheduleFinalCleanup(String who, String what) { diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 4b65934..e8f6818 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -66,7 +66,7 @@ interface INotificationManager boolean setZenModeConfig(in ZenModeConfig config); oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions); oneway void requestZenModeConditions(in IConditionListener callback, int relevance); - oneway void setZenModeCondition(in Uri conditionId); + oneway void setZenModeCondition(in Condition condition); oneway void setAutomaticZenModeConditions(in Uri[] conditionIds); Condition[] getAutomaticZenModeConditions(); }
\ No newline at end of file diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index db91742a..50e3a10 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -16,6 +16,7 @@ package android.app; +import android.content.Intent; import android.os.Binder; import android.os.RemoteException; import android.os.IBinder; @@ -24,7 +25,7 @@ import android.view.IOnKeyguardExitResult; import android.view.WindowManagerGlobal; /** - * Class that can be used to lock and unlock the keyboard. Get an instance of this + * Class that can be used to lock and unlock the keyboard. Get an instance of this * class by calling {@link android.content.Context#getSystemService(java.lang.String)} * with argument {@link android.content.Context#KEYGUARD_SERVICE}. The * actual class to control the keyboard locking is @@ -34,6 +35,45 @@ public class KeyguardManager { private IWindowManager mWM; /** + * Intent used to prompt user for device credentials. + * @hide + */ + public static final String ACTION_CONFIRM_DEVICE_CREDENTIAL = + "android.app.action.CONFIRM_DEVICE_CREDENTIAL"; + + /** + * A CharSequence dialog title to show to the user when used with a + * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. + * @hide + */ + public static final String EXTRA_TITLE = "android.app.extra.TITLE"; + + /** + * A CharSequence description to show to the user when used with + * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. + * @hide + */ + public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION"; + + /** + * Get an intent to prompt the user to confirm credentials (pin, pattern or password) + * for the current user of the device. The caller is expected to launch this activity using + * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for + * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge. + * + * @return the intent for launching the activity or null if no password is required. + **/ + public Intent getConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) { + if (!isKeyguardSecure()) return null; + Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL); + intent.putExtra(EXTRA_TITLE, title); + intent.putExtra(EXTRA_DESCRIPTION, description); + // For security reasons, only allow this to come from system settings. + intent.setPackage("com.android.settings"); + return intent; + } + + /** * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD} * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} * instead; this allows you to seamlessly hide the keyguard as your application @@ -58,7 +98,7 @@ public class KeyguardManager { * * A good place to call this is from {@link android.app.Activity#onResume()} * - * Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager} + * Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager} * is enabled that requires a password. * * <p>This method requires the caller to hold the permission @@ -121,7 +161,7 @@ public class KeyguardManager { * permissions be requested. * * Enables you to lock or unlock the keyboard. Get an instance of this class by - * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. + * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}. * @param tag A tag that informally identifies who you are (for debugging who * is disabling he keyguard). diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 24c2835..fcfc1c4 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -199,9 +199,10 @@ public final class LoadedApk { /** * Sets application info about the system package. */ - void installSystemApplicationInfo(ApplicationInfo info) { + void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { assert info.packageName.equals("android"); mApplicationInfo = info; + mClassLoader = classLoader; } public String getPackageName() { @@ -262,10 +263,6 @@ public final class LoadedApk { if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) { final String isa = VMRuntime.getRuntime().vmInstructionSet(); try { - // TODO: We can probably do away with the isa argument since - // the AM and PM have enough information to figure this out - // themselves. If we do need it, we should match it against the - // list of devices ISAs before sending it down to installd. ActivityThread.getPackageManager().performDexOptIfNeeded(mPackageName, isa); } catch (RemoteException re) { // Ignored. diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index abe4f0a..61e105b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1419,21 +1419,6 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE"; /** - * Activity Action: Prompt the user to confirm credentials (pin, pattern or password) - * for the current user of the device. Launch this activity using - * {@link android.app.Activity#startActivityForResult(Intent, int)} and check if the - * result is {@link android.app.Activity#RESULT_OK} for a successful response to the - * challenge.<p/> - * This intent is handled by the system at a high priority and applications cannot intercept - * it.<p/> - * You can use {@link android.app.KeyguardManager#isKeyguardSecure()} to determine if the user will be - * prompted. - */ - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_CONFIRM_DEVICE_CREDENTIAL = "android.intent.action.CONFIRM_DEVICE_CREDENTIAL"; - - - /** * Specify whether the package should be uninstalled for all users. * @hide because these should not be part of normal application flow. */ @@ -3192,17 +3177,11 @@ public class Intent implements Parcelable, Cloneable { /** * A CharSequence dialog title to provide to the user when used with a - * {@link #ACTION_CHOOSER} or {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. + * {@link #ACTION_CHOOSER}. */ public static final String EXTRA_TITLE = "android.intent.extra.TITLE"; /** - * A CharSequence description to provide to the user when used with - * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}. - */ - public static final String EXTRA_DETAILS = "android.intent.extra.DETAILS"; - - /** * A Parcelable[] of {@link Intent} or * {@link android.content.pm.LabeledIntent} objects as set with * {@link #putExtra(String, Parcelable[])} of additional activities to place diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 47b74ab..6160bc2 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.NonNull; +import android.annotation.Nullable; import android.net.ProxyInfo; import android.os.Parcelable; import android.os.Parcel; @@ -124,7 +125,7 @@ public final class LinkProperties implements Parcelable { * * @return The interface name set for this link or {@code null}. */ - public String getInterfaceName() { + public @Nullable String getInterfaceName() { return mIfaceName; } diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index ee4d45e..5815fa6 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -59,5 +59,5 @@ interface INfcAdapter void registerLockscreenDispatch(INfcLockscreenDispatch lockscreenDispatch, in int[] techList); void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList); - void removeNfcUnlockHandler(IBinder b); + void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler); } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index dde2cf1..6bd5a32 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -311,7 +311,7 @@ public final class NfcAdapter { final NfcActivityManager mNfcActivityManager; final Context mContext; - final HashMap<NfcUnlockHandler, IBinder> mNfcUnlockHandlers; + final HashMap<NfcUnlockHandler, INfcUnlockHandler> mNfcUnlockHandlers; final Object mLock; /** @@ -542,7 +542,7 @@ public final class NfcAdapter { NfcAdapter(Context context) { mContext = context; mNfcActivityManager = new NfcActivityManager(this); - mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, IBinder>(); + mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>(); mLock = new Object(); } @@ -1498,27 +1498,37 @@ public final class NfcAdapter { * <p /> The parameter {@code tagTechnologies} determines which Tag technologies will be polled for * at the lockscreen. Polling for less tag technologies reduces latency, and so it is * strongly recommended to only provide the Tag technologies that the handler is expected to - * receive. + * receive. There must be at least one tag technology provided, otherwise the unlock handler + * is ignored. * * @hide */ @SystemApi public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler, String[] tagTechnologies) { - try { - INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() { - @Override - public boolean onUnlockAttempted(Tag tag) throws RemoteException { - return unlockHandler.onUnlockAttempted(tag); - } - }; + // If there are no tag technologies, don't bother adding unlock handler + if (tagTechnologies.length == 0) { + return false; + } + try { synchronized (mLock) { if (mNfcUnlockHandlers.containsKey(unlockHandler)) { - return true; + // update the tag technologies + sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler)); + mNfcUnlockHandlers.remove(unlockHandler); } - sService.addNfcUnlockHandler(iHandler, Tag.getTechCodesFromStrings(tagTechnologies)); - mNfcUnlockHandlers.put(unlockHandler, iHandler.asBinder()); + + INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() { + @Override + public boolean onUnlockAttempted(Tag tag) throws RemoteException { + return unlockHandler.onUnlockAttempted(tag); + } + }; + + sService.addNfcUnlockHandler(iHandler, + Tag.getTechCodesFromStrings(tagTechnologies)); + mNfcUnlockHandlers.put(unlockHandler, iHandler); } } catch (RemoteException e) { attemptDeadServiceRecovery(e); @@ -1542,8 +1552,7 @@ public final class NfcAdapter { try { synchronized (mLock) { if (mNfcUnlockHandlers.containsKey(unlockHandler)) { - sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler)); - mNfcUnlockHandlers.remove(unlockHandler); + sService.removeNfcUnlockHandler(mNfcUnlockHandlers.remove(unlockHandler)); } return true; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index f9e7b78..9d1a7bc 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -499,7 +499,12 @@ public class UserManager { * Sets all the user-wide restrictions for this user. * Requires the MANAGE_USERS permission. * @param restrictions the Bundle containing all the restrictions. + * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction( + * android.content.ComponentName, String)} or + * {@link android.app.admin.DevicePolicyManager#clearUserRestriction( + * android.content.ComponentName, String)} instead. */ + @Deprecated public void setUserRestrictions(Bundle restrictions) { setUserRestrictions(restrictions, Process.myUserHandle()); } @@ -509,7 +514,12 @@ public class UserManager { * Requires the MANAGE_USERS permission. * @param restrictions the Bundle containing all the restrictions. * @param userHandle the UserHandle of the user for whom to set the restrictions. + * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction( + * android.content.ComponentName, String)} or + * {@link android.app.admin.DevicePolicyManager#clearUserRestriction( + * android.content.ComponentName, String)} instead. */ + @Deprecated public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) { try { mService.setUserRestrictions(restrictions, userHandle.getIdentifier()); @@ -523,7 +533,12 @@ public class UserManager { * Requires the MANAGE_USERS permission. * @param key the key of the restriction * @param value the value for the restriction + * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction( + * android.content.ComponentName, String)} or + * {@link android.app.admin.DevicePolicyManager#clearUserRestriction( + * android.content.ComponentName, String)} instead. */ + @Deprecated public void setUserRestriction(String key, boolean value) { Bundle bundle = getUserRestrictions(); bundle.putBoolean(key, value); @@ -537,7 +552,12 @@ public class UserManager { * @param key the key of the restriction * @param value the value for the restriction * @param userHandle the user whose restriction is to be changed. + * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction( + * android.content.ComponentName, String)} or + * {@link android.app.admin.DevicePolicyManager#clearUserRestriction( + * android.content.ComponentName, String)} instead. */ + @Deprecated public void setUserRestriction(String key, boolean value, UserHandle userHandle) { Bundle bundle = getUserRestrictions(userHandle); bundle.putBoolean(key, value); @@ -718,7 +738,7 @@ public class UserManager { /** * Returns list of the profiles of userHandle including * userHandle itself. - * Note that it this returns both enabled and not enabled profiles. See + * Note that this returns both enabled and not enabled profiles. See * {@link #getUserProfiles()} if you need only the enabled ones. * * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8886559..c2a3012 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2667,6 +2667,16 @@ public final class Settings { }; /** + * These entries are considered common between the personal and the managed profile, + * since the managed profile doesn't get to change them. + * @hide + */ + public static final String[] CLONE_TO_MANAGED_PROFILE = { + DATE_FORMAT, + TIME_12_24 + }; + + /** * When to use Wi-Fi calling * * @see android.telephony.TelephonyManager.WifiCallingChoices @@ -4797,6 +4807,26 @@ public final class Settings { }; /** + * These entries are considered common between the personal and the managed profile, + * since the managed profile doesn't get to change them. + * @hide + */ + public static final String[] CLONE_TO_MANAGED_PROFILE = { + ACCESSIBILITY_ENABLED, + ALLOW_MOCK_LOCATION, + ALLOWED_GEOLOCATION_ORIGINS, + DEFAULT_INPUT_METHOD, + ENABLED_ACCESSIBILITY_SERVICES, + ENABLED_INPUT_METHODS, + LOCATION_MODE, + LOCATION_PROVIDERS_ALLOWED, + LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + SELECTED_INPUT_METHOD_SUBTYPE, + SELECTED_SPELL_CHECKER, + SELECTED_SPELL_CHECKER_SUBTYPE + }; + + /** * Helper method for determining if a location provider is enabled. * * @param cr the content resolver to use diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index cc09653..872f911 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -73,9 +73,14 @@ public class ZenModeConfig implements Parcelable { private static final String CONDITION_TAG = "condition"; private static final String CONDITION_ATT_COMPONENT = "component"; private static final String CONDITION_ATT_ID = "id"; + private static final String CONDITION_ATT_SUMMARY = "summary"; + private static final String CONDITION_ATT_LINE1 = "line1"; + private static final String CONDITION_ATT_LINE2 = "line2"; + private static final String CONDITION_ATT_ICON = "icon"; + private static final String CONDITION_ATT_STATE = "state"; + private static final String CONDITION_ATT_FLAGS = "flags"; private static final String EXIT_CONDITION_TAG = "exitCondition"; - private static final String EXIT_CONDITION_ATT_ID = "id"; private static final String EXIT_CONDITION_ATT_COMPONENT = "component"; public boolean allowCalls; @@ -83,13 +88,13 @@ public class ZenModeConfig implements Parcelable { public int allowFrom = SOURCE_ANYONE; public String sleepMode; - public int sleepStartHour; - public int sleepStartMinute; + public int sleepStartHour; // 0-23 + public int sleepStartMinute; // 0-59 public int sleepEndHour; public int sleepEndMinute; public ComponentName[] conditionComponents; public Uri[] conditionIds; - public Uri exitConditionId; + public Condition exitCondition; public ComponentName exitConditionComponent; public ZenModeConfig() { } @@ -115,7 +120,7 @@ public class ZenModeConfig implements Parcelable { source.readTypedArray(conditionIds, Uri.CREATOR); } allowFrom = source.readInt(); - exitConditionId = source.readParcelable(null); + exitCondition = source.readParcelable(null); exitConditionComponent = source.readParcelable(null); } @@ -146,7 +151,7 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(0); } dest.writeInt(allowFrom); - dest.writeParcelable(exitConditionId, 0); + dest.writeParcelable(exitCondition, 0); dest.writeParcelable(exitConditionComponent, 0); } @@ -163,7 +168,7 @@ public class ZenModeConfig implements Parcelable { .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents)) .append(",conditionIds=") .append(conditionIds == null ? null : TextUtils.join(",", conditionIds)) - .append(",exitConditionId=").append(exitConditionId) + .append(",exitCondition=").append(exitCondition) .append(",exitConditionComponent=").append(exitConditionComponent) .append(']').toString(); } @@ -196,7 +201,7 @@ public class ZenModeConfig implements Parcelable { && other.sleepEndMinute == sleepEndMinute && Objects.deepEquals(other.conditionComponents, conditionComponents) && Objects.deepEquals(other.conditionIds, conditionIds) - && Objects.equals(other.exitConditionId, exitConditionId) + && Objects.equals(other.exitCondition, exitCondition) && Objects.equals(other.exitConditionComponent, exitConditionComponent); } @@ -205,7 +210,7 @@ public class ZenModeConfig implements Parcelable { return Objects.hash(allowCalls, allowMessages, allowFrom, sleepMode, sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds), - exitConditionId, exitConditionComponent); + exitCondition, exitConditionComponent); } public boolean isValid() { @@ -294,9 +299,11 @@ public class ZenModeConfig implements Parcelable { conditionIds.add(conditionId); } } else if (EXIT_CONDITION_TAG.equals(tag)) { - rt.exitConditionId = safeUri(parser, EXIT_CONDITION_ATT_ID); - rt.exitConditionComponent = - safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT); + rt.exitCondition = readConditionXml(parser); + if (rt.exitCondition != null) { + rt.exitConditionComponent = + safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT); + } } } } @@ -333,16 +340,42 @@ public class ZenModeConfig implements Parcelable { out.endTag(null, CONDITION_TAG); } } - if (exitConditionId != null && exitConditionComponent != null) { + if (exitCondition != null && exitConditionComponent != null) { out.startTag(null, EXIT_CONDITION_TAG); - out.attribute(null, EXIT_CONDITION_ATT_ID, exitConditionId.toString()); out.attribute(null, EXIT_CONDITION_ATT_COMPONENT, exitConditionComponent.flattenToString()); + writeConditionXml(exitCondition, out); out.endTag(null, EXIT_CONDITION_TAG); } out.endTag(null, ZEN_TAG); } + public static Condition readConditionXml(XmlPullParser parser) { + final Uri id = safeUri(parser, CONDITION_ATT_ID); + final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY); + final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1); + final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2); + final int icon = safeInt(parser, CONDITION_ATT_ICON, -1); + final int state = safeInt(parser, CONDITION_ATT_STATE, -1); + final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1); + try { + return new Condition(id, summary, line1, line2, icon, state, flags); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Unable to read condition xml", e); + return null; + } + } + + public static void writeConditionXml(Condition c, XmlSerializer out) throws IOException { + out.attribute(null, CONDITION_ATT_ID, c.id.toString()); + out.attribute(null, CONDITION_ATT_SUMMARY, c.summary); + out.attribute(null, CONDITION_ATT_LINE1, c.line1); + out.attribute(null, CONDITION_ATT_LINE2, c.line2); + out.attribute(null, CONDITION_ATT_ICON, Integer.toString(c.icon)); + out.attribute(null, CONDITION_ATT_STATE, Integer.toString(c.state)); + out.attribute(null, CONDITION_ATT_FLAGS, Integer.toString(c.flags)); + } + public static boolean isValidHour(int val) { return val >= 0 && val < 24; } @@ -403,21 +436,31 @@ public class ZenModeConfig implements Parcelable { } }; - // Built-in countdown conditions, e.g. condition://android/countdown/1399917958951 + public DowntimeInfo toDowntimeInfo() { + final DowntimeInfo downtime = new DowntimeInfo(); + downtime.startHour = sleepStartHour; + downtime.startMinute = sleepStartMinute; + downtime.endHour = sleepEndHour; + downtime.endMinute = sleepEndMinute; + return downtime; + } + + // For built-in conditions + private static final String SYSTEM_AUTHORITY = "android"; - private static final String COUNTDOWN_AUTHORITY = "android"; + // Built-in countdown conditions, e.g. condition://android/countdown/1399917958951 private static final String COUNTDOWN_PATH = "countdown"; public static Uri toCountdownConditionId(long time) { return new Uri.Builder().scheme(Condition.SCHEME) - .authority(COUNTDOWN_AUTHORITY) + .authority(SYSTEM_AUTHORITY) .appendPath(COUNTDOWN_PATH) .appendPath(Long.toString(time)) .build(); } public static long tryParseCountdownConditionId(Uri conditionId) { - if (!Condition.isValidId(conditionId, COUNTDOWN_AUTHORITY)) return 0; + if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)) return 0; if (conditionId.getPathSegments().size() != 2 || !COUNTDOWN_PATH.equals(conditionId.getPathSegments().get(0))) return 0; try { @@ -431,4 +474,68 @@ public class ZenModeConfig implements Parcelable { public static boolean isValidCountdownConditionId(Uri conditionId) { return tryParseCountdownConditionId(conditionId) != 0; } + + // Built-in downtime conditions, e.g. condition://android/downtime?start=10.00&end=7.00 + private static final String DOWNTIME_PATH = "downtime"; + + public static Uri toDowntimeConditionId(DowntimeInfo downtime) { + return new Uri.Builder().scheme(Condition.SCHEME) + .authority(SYSTEM_AUTHORITY) + .appendPath(DOWNTIME_PATH) + .appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute) + .appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute) + .build(); + } + + public static DowntimeInfo tryParseDowntimeConditionId(Uri conditionId) { + if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY) + || conditionId.getPathSegments().size() != 1 + || !DOWNTIME_PATH.equals(conditionId.getPathSegments().get(0))) { + return null; + } + final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start")); + final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end")); + if (start == null || end == null) return null; + final DowntimeInfo downtime = new DowntimeInfo(); + downtime.startHour = start[0]; + downtime.startMinute = start[1]; + downtime.endHour = end[0]; + downtime.endMinute = end[1]; + return downtime; + } + + private static int[] tryParseHourAndMinute(String value) { + if (TextUtils.isEmpty(value)) return null; + final int i = value.indexOf('.'); + if (i < 1 || i >= value.length() - 1) return null; + final int hour = tryParseInt(value.substring(0, i), -1); + final int minute = tryParseInt(value.substring(i + 1), -1); + return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null; + } + + public static boolean isValidDowntimeConditionId(Uri conditionId) { + return tryParseDowntimeConditionId(conditionId) != null; + } + + public static class DowntimeInfo { + public int startHour; // 0-23 + public int startMinute; // 0-59 + public int endHour; + public int endMinute; + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DowntimeInfo)) return false; + final DowntimeInfo other = (DowntimeInfo) o; + return startHour == other.startHour + && startMinute == other.startMinute + && endHour == other.endHour + && endMinute == other.endMinute; + } + } } diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index 51f07ec..5fe9194 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -69,12 +69,6 @@ public class TrustAgentService extends Service { "[" + getClass().getSimpleName() + "]"; private static final boolean DEBUG = false; - // Temporary workaround to allow current trust agent implementations to continue working. - // This and the code guarded by this should be removed before shipping. - // If true, calls setManagingTrust(true) after onCreate, if it wasn't already set. - // TODO: Remove this once all agents are updated. - private static final boolean SET_MANAGED_FOR_LEGACY_AGENTS = true; - /** * The {@link Intent} that must be declared as handled by the service. */ @@ -130,11 +124,6 @@ public class TrustAgentService extends Service { @Override public void onCreate() { - // TODO: Remove this once all agents are updated. - if (SET_MANAGED_FOR_LEGACY_AGENTS) { - setManagingTrust(true); - } - super.onCreate(); ComponentName component = new ComponentName(this, getClass()); try { @@ -175,7 +164,7 @@ public class TrustAgentService extends Service { * set. * * @param options Option feature bundle. - * @return true if the {@link #TrustAgentService()} supports this feature. + * @return true if the {@link TrustAgentService} supports this feature. */ public boolean onSetTrustAgentFeaturesEnabled(Bundle options) { return false; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a536b2d..a82fa65 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1320,8 +1320,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); boolean focusable = mMovement != null || getKeyListener() != null; - boolean clickable = focusable; - boolean longClickable = focusable; + boolean clickable = focusable || isClickable(); + boolean longClickable = focusable || isLongClickable(); n = a.getIndexCount(); for (int i = 0; i < n; i++) { diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index ee0d14b..a745b20 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -45,6 +45,7 @@ import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.LogWriter; @@ -3744,6 +3745,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public void noteNetworkInterfaceTypeLocked(String iface, int networkType) { + if (TextUtils.isEmpty(iface)) return; if (ConnectivityManager.isNetworkTypeMobile(networkType)) { mMobileIfaces = includeInStringArray(mMobileIfaces, iface); if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mMobileIfaces); diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java new file mode 100644 index 0000000..e3f229f --- /dev/null +++ b/core/java/com/android/internal/os/InstallerConnection.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.os; + +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.util.Slog; +import libcore.io.IoUtils; +import libcore.io.Streams; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Represents a connection to {@code installd}. Allows multiple connect and + * disconnect cycles. + * + * @hide for internal use only + */ +public class InstallerConnection { + private static final String TAG = "InstallerConnection"; + private static final boolean LOCAL_DEBUG = false; + + private InputStream mIn; + private OutputStream mOut; + private LocalSocket mSocket; + + private final byte buf[] = new byte[1024]; + + public InstallerConnection() { + } + + public synchronized String transact(String cmd) { + if (!connect()) { + Slog.e(TAG, "connection failed"); + return "-1"; + } + + if (!writeCommand(cmd)) { + /* + * If installd died and restarted in the background (unlikely but + * possible) we'll fail on the next write (this one). Try to + * reconnect and write the command one more time before giving up. + */ + Slog.e(TAG, "write command failed? reconnect!"); + if (!connect() || !writeCommand(cmd)) { + return "-1"; + } + } + if (LOCAL_DEBUG) { + Slog.i(TAG, "send: '" + cmd + "'"); + } + + final int replyLength = readReply(); + if (replyLength > 0) { + String s = new String(buf, 0, replyLength); + if (LOCAL_DEBUG) { + Slog.i(TAG, "recv: '" + s + "'"); + } + return s; + } else { + if (LOCAL_DEBUG) { + Slog.i(TAG, "fail"); + } + return "-1"; + } + } + + public int execute(String cmd) { + String res = transact(cmd); + try { + return Integer.parseInt(res); + } catch (NumberFormatException ex) { + return -1; + } + } + + public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) { + StringBuilder builder = new StringBuilder("dexopt"); + builder.append(' '); + builder.append(apkPath); + builder.append(' '); + builder.append(uid); + builder.append(isPublic ? " 1" : " 0"); + builder.append(" *"); // No pkgName arg present + builder.append(' '); + builder.append(instructionSet); + return execute(builder.toString()); + } + + public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) { + StringBuilder builder = new StringBuilder("patchoat"); + builder.append(' '); + builder.append(apkPath); + builder.append(' '); + builder.append(uid); + builder.append(isPublic ? " 1" : " 0"); + builder.append(" *"); // No pkgName arg present + builder.append(' '); + builder.append(instructionSet); + return execute(builder.toString()); + } + + private boolean connect() { + if (mSocket != null) { + return true; + } + Slog.i(TAG, "connecting..."); + try { + mSocket = new LocalSocket(); + + LocalSocketAddress address = new LocalSocketAddress("installd", + LocalSocketAddress.Namespace.RESERVED); + + mSocket.connect(address); + + mIn = mSocket.getInputStream(); + mOut = mSocket.getOutputStream(); + } catch (IOException ex) { + disconnect(); + return false; + } + return true; + } + + public void disconnect() { + Slog.i(TAG, "disconnecting..."); + IoUtils.closeQuietly(mSocket); + IoUtils.closeQuietly(mIn); + IoUtils.closeQuietly(mOut); + + mSocket = null; + mIn = null; + mOut = null; + } + + + private boolean readFully(byte[] buffer, int len) { + try { + Streams.readFully(mIn, buffer, 0, len); + } catch (IOException ioe) { + Slog.e(TAG, "read exception"); + disconnect(); + return false; + } + + if (LOCAL_DEBUG) { + Slog.i(TAG, "read " + len + " bytes"); + } + + return true; + } + + private int readReply() { + if (!readFully(buf, 2)) { + return -1; + } + + final int len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); + if ((len < 1) || (len > buf.length)) { + Slog.e(TAG, "invalid reply length (" + len + ")"); + disconnect(); + return -1; + } + + if (!readFully(buf, len)) { + return -1; + } + + return len; + } + + private boolean writeCommand(String cmdString) { + final byte[] cmd = cmdString.getBytes(); + final int len = cmd.length; + if ((len < 1) || (len > buf.length)) { + return false; + } + + buf[0] = (byte) (len & 0xff); + buf[1] = (byte) ((len >> 8) & 0xff); + try { + mOut.write(buf, 0, 2); + mOut.write(cmd, 0, len); + } catch (IOException ex) { + Slog.e(TAG, "write error"); + disconnect(); + return false; + } + return true; + } +} diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 4a26b4b..d35fce4 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -192,13 +192,14 @@ public class RuntimeInit { * * @param className Fully-qualified class name * @param argv Argument vector for main() + * @param classLoader the classLoader to load {@className} with */ - private static void invokeStaticMain(String className, String[] argv) + private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { Class<?> cl; try { - cl = Class.forName(className); + cl = Class.forName(className, true, classLoader); } catch (ClassNotFoundException ex) { throw new RuntimeException( "Missing class when invoking static main " + className, @@ -263,7 +264,7 @@ public class RuntimeInit { * @param targetSdkVersion target SDK version * @param argv arg strings */ - public static final void zygoteInit(int targetSdkVersion, String[] argv) + public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote"); @@ -272,7 +273,7 @@ public class RuntimeInit { commonInit(); nativeZygoteInit(); - applicationInit(targetSdkVersion, argv); + applicationInit(targetSdkVersion, argv, classLoader); } /** @@ -290,10 +291,10 @@ public class RuntimeInit { throws ZygoteInit.MethodAndArgsCaller { if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from wrapper"); - applicationInit(targetSdkVersion, argv); + applicationInit(targetSdkVersion, argv, null); } - private static void applicationInit(int targetSdkVersion, String[] argv) + private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) throws ZygoteInit.MethodAndArgsCaller { // If the application calls System.exit(), terminate the process // immediately without running any shutdown hooks. It is not possible to @@ -317,7 +318,7 @@ public class RuntimeInit { } // Remaining arguments are passed to the start class's static main - invokeStaticMain(args.startClass, args.startArgs); + invokeStaticMain(args.startClass, args.startArgs, classLoader); } /** diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 0c48368..43ebb3d 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -863,7 +863,7 @@ class ZygoteConnection { pipeFd, parsedArgs.remainingArgs); } else { RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, - parsedArgs.remainingArgs); + parsedArgs.remainingArgs, null /* classLoader */); } } else { String className; diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index eea4201..051de6e 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -34,8 +34,11 @@ import android.system.Os; import android.system.OsConstants; import android.util.EventLog; import android.util.Log; +import android.util.Slog; import android.webkit.WebViewFactory; +import dalvik.system.DexFile; +import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; import libcore.io.IoUtils; @@ -493,21 +496,69 @@ public class ZygoteInit { Process.setArgV0(parsedArgs.niceName); } + final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH"); + if (systemServerClasspath != null) { + performSystemServerDexOpt(systemServerClasspath); + } + if (parsedArgs.invokeWith != null) { + String[] args = parsedArgs.remainingArgs; + // If we have a non-null system server class path, we'll have to duplicate the + // existing arguments and append the classpath to it. ART will handle the classpath + // correctly when we exec a new process. + if (systemServerClasspath != null) { + String[] amendedArgs = new String[args.length + 2]; + amendedArgs[0] = "-cp"; + amendedArgs[1] = systemServerClasspath; + System.arraycopy(parsedArgs.remainingArgs, 0, amendedArgs, 2, parsedArgs.remainingArgs.length); + } + WrapperInit.execApplication(parsedArgs.invokeWith, parsedArgs.niceName, parsedArgs.targetSdkVersion, - null, parsedArgs.remainingArgs); + null, args); } else { + ClassLoader cl = null; + if (systemServerClasspath != null) { + cl = new PathClassLoader(systemServerClasspath, ClassLoader.getSystemClassLoader()); + Thread.currentThread().setContextClassLoader(cl); + } + /* * Pass the remaining arguments to SystemServer. */ - RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs); + RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl); } /* should never reach here */ } /** + * Performs dex-opt on the elements of {@code classPath}, if needed. We + * choose the instruction set of the current runtime. + */ + private static void performSystemServerDexOpt(String classPath) { + final String[] classPathElements = classPath.split(":"); + final InstallerConnection installer = new InstallerConnection(); + final String instructionSet = VMRuntime.getRuntime().vmInstructionSet(); + + try { + for (String classPathElement : classPathElements) { + final byte dexopt = DexFile.isDexOptNeededInternal(classPathElement, "*", instructionSet, + false /* defer */); + if (dexopt == DexFile.DEXOPT_NEEDED) { + installer.dexopt(classPathElement, Process.SYSTEM_UID, false, instructionSet); + } else if (dexopt == DexFile.PATCHOAT_NEEDED) { + installer.patchoat(classPathElement, Process.SYSTEM_UID, false, instructionSet); + } + } + } catch (IOException ioe) { + throw new RuntimeException("Error starting system_server", ioe); + } finally { + installer.disconnect(); + } + } + + /** * Prepare the arguments and fork for the system server process. */ private static boolean startSystemServer(String abiList, String socketName) diff --git a/services/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index 7249985..7249985 100644 --- a/services/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 480383b..2106d38 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -246,6 +246,7 @@ LOCAL_SHARED_LIBRARIES := \ libminikin \ libstlport \ libprocessgroup \ + libnativebridge \ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 90c66d7..79b8542 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -161,10 +161,8 @@ extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_StaticLayout(JNIEnv *env); extern int register_android_text_AndroidBidi(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); -extern int register_android_server_fingerprint_FingerprintService(JNIEnv* env); -extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env); -extern int register_android_server_Watchdog(JNIEnv* env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); +extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env); extern int register_android_backup_BackupDataInput(JNIEnv *env); extern int register_android_backup_BackupDataOutput(JNIEnv *env); @@ -804,7 +802,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) "-Xmx", "-Xcompiler-option"); if (skip_compilation) { addOption("-Xcompiler-option"); - addOption("--compiler-filter=interpret-only"); + addOption("--compiler-filter=verify-none"); } else { parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf, "--compiler-filter=", "-Xcompiler-option"); @@ -1338,9 +1336,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_media_ToneGenerator), REG_JNI(register_android_opengl_classes), - REG_JNI(register_android_server_fingerprint_FingerprintService), REG_JNI(register_android_server_NetworkManagementSocketTagger), - REG_JNI(register_android_server_Watchdog), REG_JNI(register_android_ddm_DdmHandleNativeHeap), REG_JNI(register_android_backup_BackupDataInput), REG_JNI(register_android_backup_BackupDataOutput), diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp index 9c44093..633a207 100644 --- a/core/jni/android_app_NativeActivity.cpp +++ b/core/jni/android_app_NativeActivity.cpp @@ -38,6 +38,8 @@ #include "android_view_InputChannel.h" #include "android_view_KeyEvent.h" +#include "nativebridge/native_bridge.h" + #define LOG_TRACE(...) //#define LOG_TRACE(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__) @@ -251,17 +253,29 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName const char* pathStr = env->GetStringUTFChars(path, NULL); NativeCode* code = NULL; - + bool needNativeBridge = false; + void* handle = dlopen(pathStr, RTLD_LAZY); - + if (handle == NULL) { + if (NativeBridgeIsSupported(pathStr)) { + handle = NativeBridgeLoadLibrary(pathStr, RTLD_LAZY); + needNativeBridge = true; + } + } env->ReleaseStringUTFChars(path, pathStr); - + if (handle != NULL) { + void* funcPtr = NULL; const char* funcStr = env->GetStringUTFChars(funcName, NULL); - code = new NativeCode(handle, (ANativeActivity_createFunc*) - dlsym(handle, funcStr)); + if (needNativeBridge) { + funcPtr = NativeBridgeGetTrampoline(handle, funcStr, NULL, 0); + } else { + funcPtr = dlsym(handle, funcStr); + } + + code = new NativeCode(handle, (ANativeActivity_createFunc*)funcPtr); env->ReleaseStringUTFChars(funcName, funcStr); - + if (code->createActivityFunc == NULL) { ALOGW("ANativeActivity_onCreate not found"); delete code; diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 74992ed..8065a9c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5523,4 +5523,7 @@ <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description --> <string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging</string> + + <!-- [CHAR_LIMIT=NONE] Zen mode: Condition summary for built-in downtime condition, if active --> + <string name="downtime_condition_summary">Until your downtime ends at <xliff:g id="formattedTime" example="10.00 PM">%1$s</xliff:g></string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a4bec17..2cd0948 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1903,6 +1903,7 @@ <java-symbol type="string" name="timepicker_transition_mid_radius_multiplier" /> <java-symbol type="string" name="timepicker_transition_end_radius_multiplier" /> <java-symbol type="string" name="battery_saver_description" /> + <java-symbol type="string" name="downtime_condition_summary" /> <java-symbol type="string" name="item_is_selected" /> <java-symbol type="string" name="day_of_week_label_typeface" /> diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java index fc38e8a..14aa570 100644 --- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java @@ -119,11 +119,11 @@ public class AnimatedStateListDrawable extends StateListDrawable { * * @param fromId Unique identifier of the starting keyframe * @param toId Unique identifier of the ending keyframe - * @param transition An animatable drawable to use as a transition, may not be null + * @param transition An {@link Animatable} drawable to use as a transition, may not be null * @param reversible Whether the transition can be reversed */ - public void addTransition(int fromId, int toId, @NonNull Drawable transition, - boolean reversible) { + public <T extends Drawable & Animatable> void addTransition(int fromId, int toId, + @NonNull T transition, boolean reversible) { if (transition == null) { throw new IllegalArgumentException("Transition drawable must not be null"); } diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index f5e63ae..40adf94 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -16,6 +16,7 @@ package android.graphics.drawable; +import android.annotation.NonNull; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; @@ -27,6 +28,7 @@ import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; import android.graphics.Matrix; +import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; @@ -598,6 +600,16 @@ public class BitmapDrawable extends Drawable { } @Override + public void getOutline(@NonNull Outline outline) { + super.getOutline(outline); + if (mBitmapState.mBitmap.hasAlpha()) { + // Bitmaps with alpha can't report a non-0 alpha, + // since they may not fill their rectangular bounds + outline.setAlpha(0.0f); + } + } + + @Override public void setAlpha(int alpha) { final int oldAlpha = mBitmapState.mPaint.getAlpha(); if (alpha != oldAlpha) { diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 766e681..9ac6927 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -49,7 +49,7 @@ import java.util.ArrayList; import java.util.Stack; /** - * This lets you create a drawable based on an XML vector graphic It can be + * This lets you create a drawable based on an XML vector graphic. It can be * defined in an XML file with the <code><vector></code> element. * <p/> * The vector drawable has the following elements: diff --git a/graphics/java/android/graphics/drawable/shapes/Shape.java b/graphics/java/android/graphics/drawable/shapes/Shape.java index 589fbaa..eab8666 100644 --- a/graphics/java/android/graphics/drawable/shapes/Shape.java +++ b/graphics/java/android/graphics/drawable/shapes/Shape.java @@ -16,6 +16,7 @@ package android.graphics.drawable.shapes; +import android.annotation.NonNull; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Paint; @@ -93,11 +94,12 @@ public abstract class Shape implements Cloneable { protected void onResize(float width, float height) {} /** - * Compute the Outline of the shape. + * Compute the Outline of the shape and return it in the supplied Outline + * parameter. The default implementation does nothing and {@code outline} is not changed. * - * The default implementation does not supply an outline. + * @param outline The Outline to be populated with the result. Should not be null. */ - public void getOutline(Outline outline) {} + public void getOutline(@NonNull Outline outline) {} @Override public Shape clone() throws CloneNotSupportedException { diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 7661d58..f963a4e 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -802,11 +802,16 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const const String8 ResStringPool::string8ObjectAt(size_t idx) const { size_t len; - const char *str = (const char*)string8At(idx, &len); + const char *str = string8At(idx, &len); if (str != NULL) { - return String8(str); + return String8(str, len); } - return String8(stringAt(idx, &len)); + + const char16_t *str16 = stringAt(idx, &len); + if (str16 != NULL) { + return String8(str16, len); + } + return String8(); } const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const @@ -2691,6 +2696,9 @@ String8 ResTable_config::toString() const { case ResTable_config::DENSITY_XXHIGH: res.append("xxhdpi"); break; + case ResTable_config::DENSITY_XXXHIGH: + res.append("xxxhdpi"); + break; case ResTable_config::DENSITY_NONE: res.append("nodpi"); break; diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index 5ecd77a..78d569d 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "RT-Animator" - #include "Animator.h" #include <inttypes.h> diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp index 15bed58..054a164 100644 --- a/libs/hwui/DamageAccumulator.cpp +++ b/libs/hwui/DamageAccumulator.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "DamageAccumulator" - #include "DamageAccumulator.h" #include <cutils/log.h> @@ -26,12 +24,6 @@ namespace android { namespace uirenderer { -NullDamageAccumulator NullDamageAccumulator::sInstance; - -NullDamageAccumulator* NullDamageAccumulator::instance() { - return &sInstance; -} - enum TransformType { TransformInvalid = 0, TransformRenderNode, @@ -60,6 +52,30 @@ DamageAccumulator::DamageAccumulator() { mHead->type = TransformNone; } +static void computeTransformImpl(const DirtyStack* currentFrame, Matrix4* outMatrix) { + if (currentFrame->prev != currentFrame) { + computeTransformImpl(currentFrame->prev, outMatrix); + } + switch (currentFrame->type) { + case TransformRenderNode: + currentFrame->renderNode->applyViewPropertyTransforms(*outMatrix); + break; + case TransformMatrix4: + outMatrix->multiply(*currentFrame->matrix4); + break; + case TransformNone: + // nothing to be done + break; + default: + LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d", currentFrame->type); + } +} + +void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const { + outMatrix->loadIdentity(); + computeTransformImpl(mHead, outMatrix); +} + void DamageAccumulator::pushCommon() { if (!mHead->next) { DirtyStack* nextFrame = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack)); diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h index 90d9425..6f0bd8c 100644 --- a/libs/hwui/DamageAccumulator.h +++ b/libs/hwui/DamageAccumulator.h @@ -31,18 +31,7 @@ struct DirtyStack; class RenderNode; class Matrix4; -class IDamageAccumulator { -public: - virtual void pushTransform(const RenderNode* transform) = 0; - virtual void pushTransform(const Matrix4* transform) = 0; - virtual void popTransform() = 0; - virtual void dirty(float left, float top, float right, float bottom) = 0; - virtual void peekAtDirty(SkRect* dest) = 0; -protected: - virtual ~IDamageAccumulator() {} -}; - -class DamageAccumulator : public IDamageAccumulator { +class DamageAccumulator { PREVENT_COPY_AND_ASSIGN(DamageAccumulator); public: DamageAccumulator(); @@ -51,17 +40,19 @@ public: // Push a transform node onto the stack. This should be called prior // to any dirty() calls. Subsequent calls to dirty() // will be affected by the transform when popTransform() is called. - virtual void pushTransform(const RenderNode* transform); - virtual void pushTransform(const Matrix4* transform); + void pushTransform(const RenderNode* transform); + void pushTransform(const Matrix4* transform); // Pops a transform node from the stack, propagating the dirty rect // up to the parent node. Returns the IDamageTransform that was just applied - virtual void popTransform(); + void popTransform(); - virtual void dirty(float left, float top, float right, float bottom); + void dirty(float left, float top, float right, float bottom); // Returns the current dirty area, *NOT* transformed by pushed transforms - virtual void peekAtDirty(SkRect* dest); + void peekAtDirty(SkRect* dest); + + void computeCurrentTransform(Matrix4* outMatrix) const; void finish(SkRect* totalDirty); @@ -74,24 +65,6 @@ private: DirtyStack* mHead; }; -class NullDamageAccumulator : public IDamageAccumulator { - PREVENT_COPY_AND_ASSIGN(NullDamageAccumulator); -public: - virtual void pushTransform(const RenderNode* transform) { } - virtual void pushTransform(const Matrix4* transform) { } - virtual void popTransform() { } - virtual void dirty(float left, float top, float right, float bottom) { } - virtual void peekAtDirty(SkRect* dest) { dest->setEmpty(); } - - ANDROID_API static NullDamageAccumulator* instance(); - -private: - NullDamageAccumulator() {} - ~NullDamageAccumulator() {} - - static NullDamageAccumulator sInstance; -}; - } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index f0a6e55..94162fc 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -66,7 +66,7 @@ status_t DisplayListRenderer::prepareDirty(float left, float top, "prepareDirty called a second time during a recording!"); mDisplayListData = new DisplayListData(); - initializeSaveStack(0, 0, getWidth(), getHeight()); + initializeSaveStack(0, 0, getWidth(), getHeight(), Vector3()); mDirtyClip = opaque; mRestoreSaveCount = -1; diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index d05cabc..8639ae1 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -53,6 +53,7 @@ Layer::Layer(RenderState& renderState, const uint32_t layerWidth, const uint32_t deferredList = NULL; convexMask = NULL; caches.resourceCache.incrementRefcount(this); + rendererLightPosDirty = true; } Layer::~Layer() { @@ -80,6 +81,17 @@ void Layer::requireRenderer() { } } +void Layer::updateLightPosFromRenderer(const OpenGLRenderer& rootRenderer) { + if (renderer && rendererLightPosDirty) { + // re-init renderer's light position, based upon last cached location in window + Vector3 lightPos = rootRenderer.getLightCenter(); + cachedInvTransformInWindow.mapPoint3d(lightPos); + renderer->initLight(lightPos, rootRenderer.getLightRadius(), + rootRenderer.getAmbientShadowAlpha(), rootRenderer.getSpotShadowAlpha()); + rendererLightPosDirty = false; + } +} + bool Layer::resize(const uint32_t width, const uint32_t height) { uint32_t desiredWidth = computeIdealWidth(width); uint32_t desiredHeight = computeIdealWidth(height); @@ -203,7 +215,8 @@ void Layer::allocateTexture() { } } -void Layer::defer() { +void Layer::defer(const OpenGLRenderer& rootRenderer) { + updateLightPosFromRenderer(rootRenderer); const float width = layer.getWidth(); const float height = layer.getHeight(); @@ -253,7 +266,8 @@ void Layer::flush() { } } -void Layer::render() { +void Layer::render(const OpenGLRenderer& rootRenderer) { + updateLightPosFromRenderer(rootRenderer); renderer->setViewport(layer.getWidth(), layer.getHeight()); renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 0bf05d0..38c29c7 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -86,6 +86,11 @@ public: regionRect.translate(layer.left, layer.top); } + void setWindowTransform(Matrix4& windowTransform) { + cachedInvTransformInWindow.loadInverse(windowTransform); + rendererLightPosDirty = true; + } + void updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom); inline uint32_t getWidth() const { @@ -257,10 +262,10 @@ public: return transform; } - void defer(); + void defer(const OpenGLRenderer& rootRenderer); void cancelDefer(); void flush(); - void render(); + void render(const OpenGLRenderer& rootRenderer); /** * Bounds of the layer. @@ -304,6 +309,7 @@ public: private: void requireRenderer(); + void updateLightPosFromRenderer(const OpenGLRenderer& rootRenderer); Caches& caches; @@ -383,6 +389,12 @@ private: mat4 transform; /** + * Cached transform of layer in window, updated only on creation / resize + */ + mat4 cachedInvTransformInWindow; + bool rendererLightPosDirty; + + /** * Used to defer display lists when the layer is updated with a * display list. */ diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 3fcfbc1..721ab3d 100755 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -188,8 +188,7 @@ void OpenGLRenderer::onViewportInitialized() { void OpenGLRenderer::setupFrameState(float left, float top, float right, float bottom, bool opaque) { mCaches.clearGarbage(); - - initializeSaveStack(left, top, right, bottom); + initializeSaveStack(left, top, right, bottom, mLightCenter); mOpaque = opaque; mTilingClip.set(left, top, right, bottom); } @@ -481,9 +480,9 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { } if (CC_UNLIKELY(inFrame || mCaches.drawDeferDisabled)) { - layer->render(); + layer->render(*this); } else { - layer->defer(); + layer->defer(*this); } if (inFrame) { diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index fc95c18..e9ca5d9 100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -345,8 +345,10 @@ public: } #endif - const Vector3& getLightCenter() const { return mLightCenter; } + const Vector3& getLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); } float getLightRadius() const { return mLightRadius; } + uint8_t getAmbientShadowAlpha() const { return mAmbientShadowAlpha; } + uint8_t getSpotShadowAlpha() const { return mSpotShadowAlpha; } protected: /** diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index e3732a1..0db6198 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -116,6 +116,7 @@ int RenderNode::getDebugSize() { void RenderNode::prepareTree(TreeInfo& info) { ATRACE_CALL(); + LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing"); prepareTreeImpl(info); } @@ -163,16 +164,26 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { return; } + bool transformUpdateNeeded = false; if (!mLayer) { mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight()); applyLayerPropertiesToLayer(info); damageSelf(info); + transformUpdateNeeded = true; } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) { if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) { LayerRenderer::destroyLayer(mLayer); mLayer = 0; } damageSelf(info); + transformUpdateNeeded = true; + } + + if (transformUpdateNeeded) { + // update the transform in window of the layer to reset its origin wrt light source position + Matrix4 windowTransform; + info.damageAccumulator->computeCurrentTransform(&windowTransform); + mLayer->setWindowTransform(windowTransform); } SkRect dirty; @@ -406,7 +417,7 @@ void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler) { * If true3dTransform is set to true, the transform applied to the input matrix will use true 4x4 * matrix computation instead of the Skia 3x3 matrix + camera hackery. */ -void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) { +void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) const { if (properties().getLeft() != 0 || properties().getTop() != 0) { matrix.translate(properties().getLeft(), properties().getTop()); } diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index fa310e0..afa17d5 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -174,6 +174,8 @@ public: // UI thread only! ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator); + void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const; + private: typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair; @@ -189,8 +191,6 @@ private: kPositiveZChildren }; - void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false); - void computeOrderingImpl(DrawRenderNodeOp* opState, const SkPath* outlineOfProjectionSurface, Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface, diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index 6f19275..ecc47d2 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -55,7 +55,8 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) , empty(false) , alpha(s->alpha) , roundRectClipState(s->roundRectClipState) - , mViewportData(s->mViewportData) { + , mViewportData(s->mViewportData) + , mRelativeLightCenter(s->mRelativeLightCenter) { if (saveFlags & SkCanvas::kMatrix_SaveFlag) { mTransformRoot.load(*s->transform); transform = &mTransformRoot; @@ -200,6 +201,13 @@ void Snapshot::resetClip(float left, float top, float right, float bottom) { /////////////////////////////////////////////////////////////////////////////// void Snapshot::resetTransform(float x, float y, float z) { + // before resetting, map current light pos with inverse of current transform + Vector3 center = mRelativeLightCenter; + mat4 inverse; + inverse.loadInverse(*transform); + inverse.mapPoint3d(center); + mRelativeLightCenter = center; + transform = &mTransformRoot; transform->loadTranslate(x, y, z); } diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index 98e2440..ad4ee9d 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -161,6 +161,9 @@ public: int getViewportHeight() const { return mViewportData.mHeight; } const Matrix4& getOrthoMatrix() const { return mViewportData.mOrthoMatrix; } + const Vector3& getRelativeLightCenter() const { return mRelativeLightCenter; } + void setRelativeLightCenter(const Vector3& lightCenter) { mRelativeLightCenter = lightCenter; } + /** * Sets (and replaces) the current clipping outline */ @@ -302,6 +305,7 @@ private: SkRegion mClipRegionRoot; ViewportData mViewportData; + Vector3 mRelativeLightCenter; }; // class Snapshot diff --git a/libs/hwui/StatefulBaseRenderer.cpp b/libs/hwui/StatefulBaseRenderer.cpp index dc41157..06c5ab4 100644 --- a/libs/hwui/StatefulBaseRenderer.cpp +++ b/libs/hwui/StatefulBaseRenderer.cpp @@ -35,11 +35,12 @@ StatefulBaseRenderer::StatefulBaseRenderer() } void StatefulBaseRenderer::initializeSaveStack(float clipLeft, float clipTop, - float clipRight, float clipBottom) { + float clipRight, float clipBottom, const Vector3& lightCenter) { mSnapshot = new Snapshot(mFirstSnapshot, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); mSnapshot->fbo = getTargetFbo(); + mSnapshot->setRelativeLightCenter(lightCenter); mSaveCount = 1; } diff --git a/libs/hwui/StatefulBaseRenderer.h b/libs/hwui/StatefulBaseRenderer.h index 6d83b4c..3957d36 100644 --- a/libs/hwui/StatefulBaseRenderer.h +++ b/libs/hwui/StatefulBaseRenderer.h @@ -52,7 +52,8 @@ public: * the render target. */ virtual void setViewport(int width, int height); - void initializeSaveStack(float clipLeft, float clipTop, float clipRight, float clipBottom); + void initializeSaveStack(float clipLeft, float clipTop, float clipRight, float clipBottom, + const Vector3& lightCenter); // getters bool hasRectToRectTransform() const { diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp index c5fc21f..0a9aeb8 100644 --- a/libs/hwui/TessellationCache.cpp +++ b/libs/hwui/TessellationCache.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <utils/JenkinsHash.h> #include <utils/Trace.h> diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index de09755..331f157 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -65,7 +65,7 @@ public: , frameTimeMs(0) , animationHook(NULL) , prepareTextures(mode == MODE_FULL) - , damageAccumulator(NullDamageAccumulator::instance()) + , damageAccumulator(NULL) , renderState(renderState) , renderer(NULL) , errorHandler(NULL) @@ -88,8 +88,9 @@ public: // TODO: Remove this? Currently this is used to signal to stop preparing // textures if we run out of cache space. bool prepareTextures; - // Must not be null - IDamageAccumulator* damageAccumulator; + + // Must not be null during actual usage + DamageAccumulator* damageAccumulator; RenderState& renderState; // The renderer that will be drawing the next frame. Use this to push any // layer updates or similar. May be NULL. diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 756f660..e673b0d 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "CanvasContext" - #include "CanvasContext.h" #include <private/hwui/DrawGlInfo.h> diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 986e808..d9b96f6 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "RenderProxy" - #include "RenderProxy.h" #include "CanvasContext.h" diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 03e98d5..403e164 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -14,10 +14,11 @@ * limitations under the License. */ -#define LOG_TAG "RenderThread" - #include "RenderThread.h" +#if defined(HAVE_PTHREADS) +#include <sys/resource.h> +#endif #include <gui/DisplayEventReceiver.h> #include <utils/Log.h> @@ -244,6 +245,9 @@ void RenderThread::requestVsync() { } bool RenderThread::threadLoop() { +#if defined(HAVE_PTHREADS) + setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY); +#endif initThreadLocals(); int timeoutMillis = -1; diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp index 3d2b0d9..cb5401c 100644 --- a/libs/hwui/thread/TaskManager.cpp +++ b/libs/hwui/thread/TaskManager.cpp @@ -15,6 +15,9 @@ */ #include <sys/sysinfo.h> +#if defined(HAVE_PTHREADS) +#include <sys/resource.h> +#endif #include "TaskManager.h" #include "Task.h" @@ -79,6 +82,13 @@ bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBa // Thread /////////////////////////////////////////////////////////////////////////////// +status_t TaskManager::WorkerThread::readyToRun() { +#if defined(HAVE_PTHREADS) + setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND); +#endif + return NO_ERROR; +} + bool TaskManager::WorkerThread::threadLoop() { mSignal.wait(); Vector<TaskWrapper> tasks; diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h index f2a216f..5a933ab 100644 --- a/libs/hwui/thread/TaskManager.h +++ b/libs/hwui/thread/TaskManager.h @@ -84,6 +84,7 @@ private: void exit(); private: + virtual status_t readyToRun(); virtual bool threadLoop(); // Lock for the list of tasks diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index a182982..aa196a9 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -290,7 +290,6 @@ public class MediaSessionLegacyHelper { if (DEBUG) { Log.d(TAG, "addMediaButtonListener already added " + pi); } - return; } holder.mMediaButtonListener = new MediaButtonListener(pi, context); // TODO determine if handling transport performer commands should also @@ -468,7 +467,11 @@ public class MediaSessionLegacyHelper { mSessions.remove(mPi); } else if (mCb == null) { mCb = new SessionCallback(); - mSession.setCallback(mCb); + Handler handler = null; + if (Looper.myLooper() == null) { + handler = new Handler(Looper.getMainLooper()); + } + mSession.setCallback(mCb, handler); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 5dc7d26..87c015c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -20,6 +20,7 @@ import java.io.FileNotFoundException; import java.security.SecureRandom; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -94,6 +95,9 @@ public class SettingsProvider extends ContentProvider { // Each defined user has their own settings protected final SparseArray<DatabaseHelper> mOpenHelpers = new SparseArray<DatabaseHelper>(); + // Keep the list of managed profiles synced here + private List<UserInfo> mManagedProfiles = null; + // Over this size we don't reject loading or saving settings but // we do consider them broken/malicious and don't keep them in // memory at least: @@ -119,6 +123,9 @@ public class SettingsProvider extends ContentProvider { private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid"; + static final HashSet<String> sSecureCloneToManagedKeys; + static final HashSet<String> sSystemCloneToManagedKeys; + static { // Keys (name column) from the 'secure' table that are now in the owner user's 'global' // table, shared across all users @@ -142,6 +149,15 @@ public class SettingsProvider extends ContentProvider { UserManager.ENSURE_VERIFY_APPS); sRestrictedKeys.put(Settings.Global.PREFERRED_NETWORK_MODE, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS); + + sSecureCloneToManagedKeys = new HashSet<String>(); + for (int i = 0; i < Settings.Secure.CLONE_TO_MANAGED_PROFILE.length; i++) { + sSecureCloneToManagedKeys.add(Settings.Secure.CLONE_TO_MANAGED_PROFILE[i]); + } + sSystemCloneToManagedKeys = new HashSet<String>(); + for (int i = 0; i < Settings.System.CLONE_TO_MANAGED_PROFILE.length; i++) { + sSystemCloneToManagedKeys.add(Settings.System.CLONE_TO_MANAGED_PROFILE[i]); + } } private boolean settingMovedToGlobal(final String name) { @@ -362,18 +378,22 @@ public class SettingsProvider extends ContentProvider { IntentFilter userFilter = new IntentFilter(); userFilter.addAction(Intent.ACTION_USER_REMOVED); + userFilter.addAction(Intent.ACTION_USER_ADDED); getContext().registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, + UserHandle.USER_OWNER); if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) { - final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_OWNER); - if (userHandle != UserHandle.USER_OWNER) { - onUserRemoved(userHandle); - } + onUserRemoved(userHandle); + } else if (intent.getAction().equals(Intent.ACTION_USER_ADDED)) { + onProfilesChanged(); } } }, userFilter); + + onProfilesChanged(); + return true; } @@ -391,6 +411,32 @@ public class SettingsProvider extends ContentProvider { sSystemCaches.delete(userHandle); sSecureCaches.delete(userHandle); sKnownMutationsInFlight.delete(userHandle); + onProfilesChanged(); + } + } + + /** + * Updates the list of managed profiles. It assumes that only the primary user + * can have managed profiles. Modify this code if that changes in the future. + */ + void onProfilesChanged() { + synchronized (this) { + mManagedProfiles = mUserManager.getProfiles(UserHandle.USER_OWNER); + if (mManagedProfiles != null) { + // Remove the primary user from the list + for (int i = mManagedProfiles.size() - 1; i >= 0; i--) { + if (mManagedProfiles.get(i).id == UserHandle.USER_OWNER) { + mManagedProfiles.remove(i); + } + } + // If there are no managed profiles, reset the variable + if (mManagedProfiles.size() == 0) { + mManagedProfiles = null; + } + } + if (LOCAL_LOGV) { + Slog.d(TAG, "Managed Profiles = " + mManagedProfiles); + } } } @@ -601,6 +647,24 @@ public class SettingsProvider extends ContentProvider { } /** + * Checks if the calling user is a managed profile of the primary user. + * Currently only the primary user (USER_OWNER) can have managed profiles. + * @param callingUser the user trying to read/write settings + * @return true if it is a managed profile of the primary user + */ + private boolean isManagedProfile(int callingUser) { + synchronized (this) { + if (mManagedProfiles == null) return false; + for (int i = mManagedProfiles.size() - 1; i >= 0; i--) { + if (mManagedProfiles.get(i).id == callingUser) { + return true; + } + } + return false; + } + } + + /** * Fast path that avoids the use of chatty remoted Cursors. */ @Override @@ -625,12 +689,18 @@ public class SettingsProvider extends ContentProvider { // Get methods if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) { if (LOCAL_LOGV) Slog.v(TAG, "call(system:" + request + ") for " + callingUser); + if (isManagedProfile(callingUser) && sSystemCloneToManagedKeys.contains(request)) { + callingUser = UserHandle.USER_OWNER; + } dbHelper = getOrEstablishDatabase(callingUser); cache = sSystemCaches.get(callingUser); return lookupValue(dbHelper, TABLE_SYSTEM, cache, request); } if (Settings.CALL_METHOD_GET_SECURE.equals(method)) { if (LOCAL_LOGV) Slog.v(TAG, "call(secure:" + request + ") for " + callingUser); + if (isManagedProfile(callingUser) && sSecureCloneToManagedKeys.contains(request)) { + callingUser = UserHandle.USER_OWNER; + } dbHelper = getOrEstablishDatabase(callingUser); cache = sSecureCaches.get(callingUser); return lookupValue(dbHelper, TABLE_SECURE, cache, request); @@ -667,13 +737,70 @@ public class SettingsProvider extends ContentProvider { values.put(Settings.NameValueTable.NAME, request); values.put(Settings.NameValueTable.VALUE, newValue); if (Settings.CALL_METHOD_PUT_SYSTEM.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for " + callingUser); + if (LOCAL_LOGV) { + Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for " + + callingUser); + } + // Extra check for USER_OWNER to optimize for the 99% + if (callingUser != UserHandle.USER_OWNER && isManagedProfile(callingUser)) { + if (sSystemCloneToManagedKeys.contains(request)) { + // Don't write these settings + return null; + } + } insertForUser(Settings.System.CONTENT_URI, values, callingUser); + // Clone the settings to the managed profiles so that notifications can be sent out + if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null + && sSystemCloneToManagedKeys.contains(request)) { + final long token = Binder.clearCallingIdentity(); + try { + for (int i = mManagedProfiles.size() - 1; i >= 0; i--) { + if (LOCAL_LOGV) { + Slog.v(TAG, "putting to additional user " + + mManagedProfiles.get(i).id); + } + insertForUser(Settings.System.CONTENT_URI, values, + mManagedProfiles.get(i).id); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } } else if (Settings.CALL_METHOD_PUT_SECURE.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for " + callingUser); + if (LOCAL_LOGV) { + Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for " + + callingUser); + } + // Extra check for USER_OWNER to optimize for the 99% + if (callingUser != UserHandle.USER_OWNER && isManagedProfile(callingUser)) { + if (sSecureCloneToManagedKeys.contains(request)) { + // Don't write these settings + return null; + } + } insertForUser(Settings.Secure.CONTENT_URI, values, callingUser); + // Clone the settings to the managed profiles so that notifications can be sent out + if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null + && sSecureCloneToManagedKeys.contains(request)) { + final long token = Binder.clearCallingIdentity(); + try { + for (int i = mManagedProfiles.size() - 1; i >= 0; i--) { + if (LOCAL_LOGV) { + Slog.v(TAG, "putting to additional user " + + mManagedProfiles.get(i).id); + } + insertForUser(Settings.Secure.CONTENT_URI, values, + mManagedProfiles.get(i).id); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } } else if (Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) { - if (LOCAL_LOGV) Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for " + callingUser); + if (LOCAL_LOGV) { + Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for " + + callingUser); + } insertForUser(Settings.Global.CONTENT_URI, values, callingUser); } else { Slog.w(TAG, "call() with invalid method: " + method); diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java index 2113c68..9fbcd7f 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java @@ -59,6 +59,7 @@ public class BrightnessController implements ToggleSlider.Listener { private boolean mAutomatic; private boolean mListening; + private boolean mExternalChange; public interface BrightnessStateChangeCallback { public void onBrightnessLevelChanged(); @@ -86,19 +87,24 @@ public class BrightnessController implements ToggleSlider.Listener { @Override public void onChange(boolean selfChange, Uri uri) { if (selfChange) return; - if (BRIGHTNESS_MODE_URI.equals(uri)) { - updateMode(); - updateSlider(); - } else if (BRIGHTNESS_URI.equals(uri) && !mAutomatic) { - updateSlider(); - } else if (BRIGHTNESS_ADJ_URI.equals(uri) && mAutomatic) { - updateSlider(); - } else { - updateMode(); - updateSlider(); - } - for (BrightnessStateChangeCallback cb : mChangeCallbacks) { - cb.onBrightnessLevelChanged(); + try { + mExternalChange = true; + if (BRIGHTNESS_MODE_URI.equals(uri)) { + updateMode(); + updateSlider(); + } else if (BRIGHTNESS_URI.equals(uri) && !mAutomatic) { + updateSlider(); + } else if (BRIGHTNESS_ADJ_URI.equals(uri) && mAutomatic) { + updateSlider(); + } else { + updateMode(); + updateSlider(); + } + for (BrightnessStateChangeCallback cb : mChangeCallbacks) { + cb.onBrightnessLevelChanged(); + } + } finally { + mExternalChange = false; } } @@ -191,6 +197,8 @@ public class BrightnessController implements ToggleSlider.Listener { @Override public void onChanged(ToggleSlider view, boolean tracking, boolean automatic, int value) { updateIcon(mAutomatic); + if (mExternalChange) return; + if (!mAutomatic) { final int val = value + mMinimumBacklight; setBrightness(val); diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java index d113139..a1704ff 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java @@ -40,35 +40,27 @@ public class BrightnessDialog extends Activity { super.onCreate(savedInstanceState); final Window window = getWindow(); - final WindowManager.LayoutParams lp = window.getAttributes(); - // Offset from the top - lp.y = getResources().getDimensionPixelOffset(R.dimen.volume_panel_top); - lp.type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; - - window.setAttributes(lp); window.setGravity(Gravity.TOP); window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.requestFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.quick_settings_brightness_dialog); + + final ImageView icon = (ImageView) findViewById(R.id.brightness_icon); + final ToggleSlider slider = (ToggleSlider) findViewById(R.id.brightness_slider); + mBrightnessController = new BrightnessController(this, icon, slider); } @Override protected void onStart() { super.onStart(); - - final ImageView icon = (ImageView) findViewById(R.id.brightness_icon); - final ToggleSlider slider = (ToggleSlider) findViewById(R.id.brightness_slider); - mBrightnessController = new BrightnessController(this, icon, slider); mBrightnessController.registerCallbacks(); } @Override protected void onStop() { super.onStop(); - mBrightnessController.unregisterCallbacks(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 99487ff..ca290e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -662,7 +662,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } public void reset() { - super.reset(); setTintColor(0); setShowingLegacyBackground(false); setBelowSpeedBump(false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 8c5151b..947d70d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -1735,6 +1735,9 @@ public abstract class BaseStatusBar extends SystemUI implements : null; // Reapply the RemoteViews + if (entry.row != null) { + entry.row.resetHeight(); + } contentView.reapply(mContext, entry.expanded, mOnClickHandler); if (bigContentView != null && entry.getBigContentView() != null) { bigContentView.reapply(mContext, entry.getBigContentView(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 43b7707..9ac20a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -88,9 +88,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mExpansionDisabled = false; mPublicLayout.reset(); mPrivateLayout.reset(); + resetHeight(); + logExpansionEvent(false, wasExpanded); + } + + public void resetHeight() { mMaxExpandHeight = 0; mWasReset = true; - logExpansionEvent(false, wasExpanded); + onHeightReset(); } @Override @@ -178,20 +183,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { * @param expand whether the system wants this notification to be expanded. */ public void setSystemExpanded(boolean expand) { - final boolean wasExpanded = isExpanded(); - mIsSystemExpanded = expand; - notifyHeightChanged(); - logExpansionEvent(false, wasExpanded); + if (expand != mIsSystemExpanded) { + final boolean wasExpanded = isExpanded(); + mIsSystemExpanded = expand; + notifyHeightChanged(); + logExpansionEvent(false, wasExpanded); + } } /** * @param expansionDisabled whether to prevent notification expansion */ public void setExpansionDisabled(boolean expansionDisabled) { - final boolean wasExpanded = isExpanded(); - mExpansionDisabled = expansionDisabled; - logExpansionEvent(false, wasExpanded); - notifyHeightChanged(); + if (expansionDisabled != mExpansionDisabled) { + final boolean wasExpanded = isExpanded(); + mExpansionDisabled = expansionDisabled; + logExpansionEvent(false, wasExpanded); + if (wasExpanded != isExpanded()) { + notifyHeightChanged(); + } + } } /** @@ -368,9 +379,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mPrivateLayout.notifyContentUpdated(); } - public boolean isShowingLayoutLayouted() { - NotificationContentView showingLayout = getShowingLayout(); - return showingLayout.getWidth() != 0; + public boolean isMaxExpandHeightInitialized() { + return mMaxExpandHeight != 0; } private NotificationContentView getShowingLayout() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 5b0bf03..127ff6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -255,7 +255,7 @@ public abstract class ExpandableView extends FrameLayout { public void setBelowSpeedBump(boolean below) { } - public void reset() { + public void onHeightReset() { if (mOnHeightChangedListener != null) { mOnHeightChangedListener.onReset(this); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index b8ac3e7..3e2a398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -25,9 +25,12 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; +import android.os.AsyncTask; import android.os.RemoteException; import android.os.UserHandle; +import android.phone.PhoneManager; import android.provider.MediaStore; +import android.telecomm.TelecommManager; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; @@ -246,7 +249,18 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } public void launchPhone() { - mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */); + TelecommManager tm = TelecommManager.from(mContext); + if (tm.isInAPhoneCall()) { + final PhoneManager pm = (PhoneManager) mContext.getSystemService(Context.PHONE_SERVICE); + AsyncTask.execute(new Runnable() { + @Override + public void run() { + pm.showCallScreen(false /* showDialpad */); + } + }); + } else { + mActivityStarter.startActivity(PHONE_INTENT, false /* dismissShade */); + } } 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 c8e943e..decaeb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -54,6 +54,11 @@ public abstract class PanelView extends FrameLayout { private float mInitialOffsetOnTouch; private float mExpandedFraction = 0; protected float mExpandedHeight = 0; + private boolean mPanelClosedOnDown; + private boolean mHasLayoutedSinceDown; + private float mUpdateFlingVelocity; + private boolean mUpdateFlingOnLayout; + private boolean mTouching; private boolean mJustPeeked; private boolean mClosing; protected boolean mTracking; @@ -76,7 +81,6 @@ public abstract class PanelView extends FrameLayout { PanelBar mBar; - protected int mMaxPanelHeight = -1; private String mViewName; private float mInitialTouchY; private float mInitialTouchX; @@ -226,6 +230,10 @@ public abstract class PanelView extends FrameLayout { mInitialOffsetOnTouch = mExpandedHeight; mTouchSlopExceeded = false; mJustPeeked = false; + mPanelClosedOnDown = mExpandedHeight == 0.0f; + mHasLayoutedSinceDown = false; + mUpdateFlingOnLayout = false; + mTouching = true; if (mVelocityTracker == null) { initVelocityTracker(); } @@ -316,6 +324,10 @@ public abstract class PanelView extends FrameLayout { boolean expand = flingExpands(vel, vectorVel); onTrackingStopped(expand); fling(vel, expand); + mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; + if (mUpdateFlingOnLayout) { + mUpdateFlingVelocity = vel; + } } else { boolean expands = onEmptySpaceClick(mInitialTouchX); onTrackingStopped(expands); @@ -325,6 +337,7 @@ public abstract class PanelView extends FrameLayout { mVelocityTracker.recycle(); mVelocityTracker = null; } + mTouching = false; break; } return !waitForTouchSlop || mTracking; @@ -383,6 +396,10 @@ public abstract class PanelView extends FrameLayout { mInitialTouchX = x; mTouchSlopExceeded = false; mJustPeeked = false; + mPanelClosedOnDown = mExpandedHeight == 0.0f; + mHasLayoutedSinceDown = false; + mUpdateFlingOnLayout = false; + mTouching = true; initVelocityTracker(); trackMovement(event); break; @@ -415,6 +432,10 @@ public abstract class PanelView extends FrameLayout { } } break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mTouching = false; + break; } return false; } @@ -444,7 +465,6 @@ public abstract class PanelView extends FrameLayout { protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); loadDimens(); - mMaxPanelHeight = -1; } /** @@ -524,27 +544,6 @@ public abstract class PanelView extends FrameLayout { return mViewName; } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)", - widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); - - // Did one of our children change size? - int newHeight = getMeasuredHeight(); - if (newHeight > mMaxPanelHeight) { - // we only adapt the max height if it's bigger - mMaxPanelHeight = newHeight; - // If the user isn't actively poking us, let's rubberband to the content - if (!mTracking && mHeightAnimator == null - && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight - && mMaxPanelHeight > 0 && mPeekAnimator == null) { - mExpandedHeight = mMaxPanelHeight; - } - } - } - public void setExpandedHeight(float height) { if (DEBUG) logf("setExpandedHeight(%.1f)", height); setExpandedHeightInternal(height + getOverExpansionPixels()); @@ -552,10 +551,14 @@ public abstract class PanelView extends FrameLayout { @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { - if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, - (int)mExpandedHeight, mMaxPanelHeight); super.onLayout(changed, left, top, right, bottom); requestPanelHeightUpdate(); + mHasLayoutedSinceDown = true; + if (mUpdateFlingOnLayout) { + abortAnimations(); + fling(mUpdateFlingVelocity, true); + mUpdateFlingOnLayout = false; + } } protected void requestPanelHeightUpdate() { @@ -567,7 +570,8 @@ public abstract class PanelView extends FrameLayout { && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight && !mPeekPending - && mPeekAnimator == null) { + && mPeekAnimator == null + && !mTouching) { setExpandedHeight(currentMaxPanelHeight); } } @@ -615,10 +619,7 @@ public abstract class PanelView extends FrameLayout { * * @return the default implementation simply returns the maximum height. */ - protected int getMaxPanelHeight() { - mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight()); - return mMaxPanelHeight; - } + protected abstract int getMaxPanelHeight(); public void setExpandedFraction(float frac) { setExpandedHeight(getMaxPanelHeight() * frac); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 04ee294..e1fd779 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -121,6 +121,7 @@ public class StatusBarWindowView extends FrameLayout { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); mStackScrollLayout.onInterceptTouchEvent(cancellation); + mNotificationPanel.onInterceptTouchEvent(cancellation); cancellation.recycle(); } return intercept; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java index 6c4fb7a..7d102ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.policy; -import android.net.Uri; import android.service.notification.Condition; public interface ZenModeController { @@ -25,15 +24,15 @@ public interface ZenModeController { void setZen(int zen); int getZen(); void requestConditions(boolean request); - void setExitConditionId(Uri exitConditionId); - Uri getExitConditionId(); + void setExitCondition(Condition exitCondition); + Condition getExitCondition(); long getNextAlarm(); void setUserId(int userId); boolean isZenAvailable(); public static class Callback { public void onZenChanged(int zen) {} - public void onExitConditionChanged(Uri exitConditionId) {} + public void onExitConditionChanged(Condition exitCondition) {} public void onConditionsChanged(Condition[] conditions) {} public void onNextAlarmChanged() {} public void onZenAvailableChanged(boolean available) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index b33e502..b0c8f26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -122,20 +122,20 @@ public class ZenModeControllerImpl implements ZenModeController { } @Override - public void setExitConditionId(Uri exitConditionId) { + public void setExitCondition(Condition exitCondition) { try { - mNoMan.setZenModeCondition(exitConditionId); + mNoMan.setZenModeCondition(exitCondition); } catch (RemoteException e) { // noop } } @Override - public Uri getExitConditionId() { + public Condition getExitCondition() { try { final ZenModeConfig config = mNoMan.getZenModeConfig(); if (config != null) { - return config.exitConditionId; + return config.exitCondition; } } catch (RemoteException e) { // noop @@ -186,10 +186,10 @@ public class ZenModeControllerImpl implements ZenModeController { } private void fireExitConditionChanged() { - final Uri exitConditionId = getExitConditionId(); - if (DEBUG) Slog.d(TAG, "exitConditionId changed: " + exitConditionId); + final Condition exitCondition = getExitCondition(); + if (DEBUG) Slog.d(TAG, "exitCondition changed: " + exitCondition); for (Callback cb : mCallbacks) { - cb.onExitConditionChanged(exitConditionId); + cb.onExitConditionChanged(exitCondition); } } 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 8e5077c..1469d73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -1914,6 +1914,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void onReset(ExpandableView view) { mRequestViewResizeAnimationOnLayout = true; + mStackScrollAlgorithm.onReset(view); } private void updateScrollPositionOnExpandInBottom(ExpandableView view) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index fc2be1a..fe855d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -758,39 +758,42 @@ public class StackScrollAlgorithm { // current height. mFirstChildMaxHeight = mFirstChildWhileExpanding.getActualHeight(); } else { - - // We are expanding the shade, expand it to its full height. - if (!isMaxSizeInitialized(mFirstChildWhileExpanding)) { - - // This child was not layouted yet, wait for a layout pass - mFirstChildWhileExpanding - .addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, - int bottom, int oldLeft, int oldTop, int oldRight, - int oldBottom) { - if (mFirstChildWhileExpanding != null) { - mFirstChildMaxHeight = getMaxAllowedChildHeight( - mFirstChildWhileExpanding); - } else { - mFirstChildMaxHeight = 0; - } - v.removeOnLayoutChangeListener(this); - } - }); - } else { - mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding); - } + updateFirstChildMaxSizeToMaxHeight(); } } else { mFirstChildMaxHeight = 0; } } + private void updateFirstChildMaxSizeToMaxHeight() { + // We are expanding the shade, expand it to its full height. + if (!isMaxSizeInitialized(mFirstChildWhileExpanding)) { + + // This child was not layouted yet, wait for a layout pass + mFirstChildWhileExpanding + .addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, + int oldBottom) { + if (mFirstChildWhileExpanding != null) { + mFirstChildMaxHeight = getMaxAllowedChildHeight( + mFirstChildWhileExpanding); + } else { + mFirstChildMaxHeight = 0; + } + v.removeOnLayoutChangeListener(this); + } + }); + } else { + mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding); + } + } + private boolean isMaxSizeInitialized(ExpandableView child) { if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; - return row.isShowingLayoutLayouted(); + return row.isMaxExpandHeightInitialized(); } return child == null || child.getWidth() != 0; } @@ -825,6 +828,12 @@ public class StackScrollAlgorithm { updatePadding(dimmed); } + public void onReset(ExpandableView view) { + if (view.equals(mFirstChildWhileExpanding)) { + updateFirstChildMaxSizeToMaxHeight(); + } + } + class StackScrollAlgorithmState { /** diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 0d837c7..c99e1fd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -97,14 +97,16 @@ public class ZenModePanel extends LinearLayout { private Callback mCallback; private ZenModeController mController; private boolean mRequestingConditions; - private Uri mExitConditionId; + private Condition mExitCondition; + private String mExitConditionText; private int mBucketIndex = -1; private boolean mExpanded; private boolean mHidden = false; private int mSessionZen; - private Uri mSessionExitConditionId; - private String mExitConditionText; + private Condition mSessionExitCondition; private long mNextAlarm; + private Condition[] mConditions; + private Condition mTimeCondition; public ZenModePanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -161,7 +163,7 @@ public class ZenModePanel extends LinearLayout { super.onAttachedToWindow(); if (DEBUG) Log.d(mTag, "onAttachedToWindow"); mSessionZen = getSelectedZen(-1); - mSessionExitConditionId = mExitConditionId; + mSessionExitCondition = copy(mExitCondition); refreshExitConditionText(); refreshNextAlarm(); updateWidgets(); @@ -172,7 +174,7 @@ public class ZenModePanel extends LinearLayout { super.onDetachedFromWindow(); if (DEBUG) Log.d(mTag, "onDetachedFromWindow"); mSessionZen = -1; - mSessionExitConditionId = null; + mSessionExitCondition = null; setExpanded(false); } @@ -199,17 +201,16 @@ public class ZenModePanel extends LinearLayout { mController.requestConditions(mRequestingConditions); } if (mRequestingConditions) { - Condition timeCondition = parseExistingTimeCondition(mExitConditionId); - if (timeCondition != null) { + mTimeCondition = parseExistingTimeCondition(mExitCondition); + if (mTimeCondition != null) { mBucketIndex = -1; } else { mBucketIndex = DEFAULT_BUCKET_INDEX; - timeCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]); + mTimeCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]); } if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex); - handleUpdateConditions(new Condition[0]); // ensures forever exists - bind(timeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); - checkForDefault(); + mConditions = null; // reset conditions + handleUpdateConditions(); } else { mZenConditions.removeAllViews(); } @@ -217,31 +218,47 @@ public class ZenModePanel extends LinearLayout { public void init(ZenModeController controller) { mController = controller; - setExitConditionId(mController.getExitConditionId()); + setExitCondition(mController.getExitCondition()); refreshExitConditionText(); mSessionZen = getSelectedZen(-1); handleUpdateZen(mController.getZen()); - if (DEBUG) Log.d(mTag, "init mExitConditionId=" + mExitConditionId); + if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition); mZenConditions.removeAllViews(); mController.addCallback(mZenCallback); } - private void setExitConditionId(Uri exitConditionId) { - if (Objects.equals(mExitConditionId, exitConditionId)) return; - mExitConditionId = exitConditionId; + private void setExitCondition(Condition exitCondition) { + if (sameConditionId(mExitCondition, exitCondition)) return; + mExitCondition = exitCondition; refreshExitConditionText(); updateWidgets(); } + private Uri getExitConditionId() { + return getConditionId(mExitCondition); + } + + private static Uri getConditionId(Condition condition) { + return condition != null ? condition.id : null; + } + + private static boolean sameConditionId(Condition lhs, Condition rhs) { + return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id); + } + + private static Condition copy(Condition condition) { + return condition == null ? null : condition.copy(); + } + private void refreshExitConditionText() { final String forever = mContext.getString(R.string.zen_mode_forever); - if (mExitConditionId == null) { + if (mExitCondition == null) { mExitConditionText = forever; - } else if (ZenModeConfig.isValidCountdownConditionId(mExitConditionId)) { - final Condition condition = parseExistingTimeCondition(mExitConditionId); + } else if (ZenModeConfig.isValidCountdownConditionId(mExitCondition.id)) { + final Condition condition = parseExistingTimeCondition(mExitCondition); mExitConditionText = condition != null ? condition.summary : forever; } else { - mExitConditionText = "(until condition ends)"; // TODO persist current description + mExitConditionText = mExitCondition.summary; } } @@ -296,7 +313,7 @@ public class ZenModePanel extends LinearLayout { mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE); mAlarmWarning.setVisibility(zenNone && expanded && hasNextAlarm ? VISIBLE : GONE); if (showAlarmWarning) { - final long exitTime = ZenModeConfig.tryParseCountdownConditionId(mExitConditionId); + final long exitTime = ZenModeConfig.tryParseCountdownConditionId(getExitConditionId()); final long now = System.currentTimeMillis(); final boolean alarmToday = time(mNextAlarm).yearDay == time(now).yearDay; final String skeleton = (alarmToday ? "" : "E") @@ -330,8 +347,9 @@ public class ZenModePanel extends LinearLayout { return t; } - private Condition parseExistingTimeCondition(Uri conditionId) { - final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); + private Condition parseExistingTimeCondition(Condition condition) { + if (condition == null) return null; + final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id); if (time == 0) return null; final long span = time - System.currentTimeMillis(); if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null; @@ -365,15 +383,36 @@ public class ZenModePanel extends LinearLayout { } private void handleUpdateConditions(Condition[] conditions) { - final int newCount = conditions == null ? 0 : conditions.length; - if (DEBUG) Log.d(mTag, "handleUpdateConditions newCount=" + newCount); - for (int i = mZenConditions.getChildCount(); i >= newCount + FIRST_CONDITION_INDEX; i--) { + mConditions = conditions; + handleUpdateConditions(); + } + + private void handleUpdateConditions() { + final int conditionCount = mConditions == null ? 0 : mConditions.length; + if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount); + for (int i = mZenConditions.getChildCount() - 1; i >= FIRST_CONDITION_INDEX; i--) { mZenConditions.removeViewAt(i); } + // forever bind(null, mZenConditions.getChildAt(FOREVER_CONDITION_INDEX)); - for (int i = 0; i < newCount; i++) { - bind(conditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i)); + // countdown + bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); + // provider conditions + boolean foundDowntime = false; + for (int i = 0; i < conditionCount; i++) { + bind(mConditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i)); + foundDowntime |= isDowntime(mConditions[i]); } + // ensure downtime exists, if active + if (isDowntime(mSessionExitCondition) && !foundDowntime) { + bind(mSessionExitCondition, null); + } + // ensure something is selected + checkForDefault(); + } + + private static boolean isDowntime(Condition c) { + return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c)); } private ConditionTag getConditionTagAt(int index) { @@ -385,7 +424,7 @@ public class ZenModePanel extends LinearLayout { for (int i = 0; i < mZenConditions.getChildCount(); i++) { if (getConditionTagAt(i).rb.isChecked()) { if (DEBUG) Log.d(mTag, "Not selecting a default, checked=" - + getConditionTagAt(i).conditionId); + + getConditionTagAt(i).condition); return; } } @@ -394,20 +433,20 @@ public class ZenModePanel extends LinearLayout { if (favoriteIndex == -1) { getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true); } else { - final Condition c = newTimeCondition(MINUTE_BUCKETS[favoriteIndex]); + mTimeCondition = newTimeCondition(MINUTE_BUCKETS[favoriteIndex]); mBucketIndex = favoriteIndex; - bind(c, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); + bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true); } } - private void handleExitConditionChanged(Uri exitCondition) { - setExitConditionId(exitCondition); - if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitConditionId); + private void handleExitConditionChanged(Condition exitCondition) { + setExitCondition(exitCondition); + if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition); final int N = mZenConditions.getChildCount(); for (int i = 0; i < N; i++) { final ConditionTag tag = getConditionTagAt(i); - tag.rb.setChecked(Objects.equals(tag.conditionId, exitCondition)); + tag.rb.setChecked(sameConditionId(tag.condition, mExitCondition)); } } @@ -427,23 +466,23 @@ public class ZenModePanel extends LinearLayout { if (tag.rb == null) { tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox); } - tag.conditionId = condition != null ? condition.id : null; + tag.condition = condition; tag.rb.setEnabled(enabled); - if (mSessionExitConditionId != null && mSessionExitConditionId.equals(tag.conditionId)) { + if (sameConditionId(mSessionExitCondition, tag.condition)) { tag.rb.setChecked(true); } tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mExpanded && isChecked) { - if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.conditionId); + if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition); final int N = mZenConditions.getChildCount(); for (int i = 0; i < N; i++) { ConditionTag childTag = getConditionTagAt(i); if (childTag == tag) continue; childTag.rb.setChecked(false); } - select(tag.conditionId); + select(tag.condition); fireInteraction(); } } @@ -479,7 +518,7 @@ public class ZenModePanel extends LinearLayout { } }); - final long time = ZenModeConfig.tryParseCountdownConditionId(tag.conditionId); + final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition)); if (time > 0) { if (mBucketIndex > -1) { button1.setEnabled(mBucketIndex > 0); @@ -504,7 +543,8 @@ public class ZenModePanel extends LinearLayout { final int N = MINUTE_BUCKETS.length; if (mBucketIndex == -1) { // not on a known index, search for the next or prev bucket by time - final long time = ZenModeConfig.tryParseCountdownConditionId(tag.conditionId); + final Uri conditionId = getConditionId(tag.condition); + final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); final long now = System.currentTimeMillis(); for (int i = 0; i < N; i++) { int j = up ? i : N - 1 - i; @@ -525,24 +565,25 @@ public class ZenModePanel extends LinearLayout { mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1))); newCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]); } - bind(newCondition, row); + mTimeCondition = newCondition; + bind(mTimeCondition, row); tag.rb.setChecked(true); - select(newCondition.id); + select(mTimeCondition); fireInteraction(); } - private void select(Uri conditionId) { - if (DEBUG) Log.d(mTag, "select " + conditionId); + private void select(Condition condition) { + if (DEBUG) Log.d(mTag, "select " + condition); if (mController != null) { - mController.setExitConditionId(conditionId); + mController.setExitCondition(condition); } - setExitConditionId(conditionId); - if (conditionId == null) { + setExitCondition(condition); + if (condition == null) { mFavorites.setMinuteIndex(-1); - } else if (ZenModeConfig.isValidCountdownConditionId(conditionId) && mBucketIndex != -1) { + } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) { mFavorites.setMinuteIndex(mBucketIndex); } - mSessionExitConditionId = conditionId; + mSessionExitCondition = copy(condition); } private void fireMoreSettings() { @@ -574,8 +615,8 @@ public class ZenModePanel extends LinearLayout { } @Override - public void onExitConditionChanged(Uri exitConditionId) { - mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitConditionId).sendToTarget(); + public void onExitConditionChanged(Condition exitCondition) { + mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget(); } @Override @@ -598,9 +639,8 @@ public class ZenModePanel extends LinearLayout { public void handleMessage(Message msg) { if (msg.what == UPDATE_CONDITIONS) { handleUpdateConditions((Condition[]) msg.obj); - checkForDefault(); } else if (msg.what == EXIT_CONDITION_CHANGED) { - handleExitConditionChanged((Uri) msg.obj); + handleExitConditionChanged((Condition) msg.obj); } else if (msg.what == UPDATE_ZEN) { handleUpdateZen(msg.arg1); } else if (msg.what == NEXT_ALARM_CHANGED) { @@ -618,7 +658,7 @@ public class ZenModePanel extends LinearLayout { // used as the view tag on condition rows private static class ConditionTag { RadioButton rb; - Uri conditionId; + Condition condition; } private final class Favorites implements OnSharedPreferenceChangeListener { diff --git a/services/appwidget/Android.mk b/services/appwidget/Android.mk index ca38f2f..e9bab4a 100644 --- a/services/appwidget/Android.mk +++ b/services/appwidget/Android.mk @@ -7,4 +7,6 @@ LOCAL_MODULE := services.appwidget LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_JAVA_LIBRARIES := services.core + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index 6e67970..6e67970 100644 --- a/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java diff --git a/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index fda6479..fda6479 100644 --- a/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ad2704a..ecd8f11 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2031,7 +2031,7 @@ public final class ActivityManagerService extends ActivityManagerNative ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS); - mSystemThread.installSystemApplicationInfo(info); + mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader()); synchronized (this) { ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index ad2bb92..bb5243c 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -1060,9 +1060,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state); final String baseIface = state.linkProperties.getInterfaceName(); - connIdents.add(Pair.create(baseIface, ident)); - if (powerSave) { - connIfaces.add(baseIface); + if (baseIface != null) { + connIdents.add(Pair.create(baseIface, ident)); + if (powerSave) { + connIfaces.add(baseIface); + } } // Stacked interfaces are considered to have same identity as @@ -1070,9 +1072,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks(); for (LinkProperties stackedLink : stackedLinks) { final String stackedIface = stackedLink.getInterfaceName(); - connIdents.add(Pair.create(stackedIface, ident)); - if (powerSave) { - connIfaces.add(stackedIface); + if (stackedIface != null) { + connIdents.add(Pair.create(stackedIface, ident)); + if (powerSave) { + connIfaces.add(stackedIface); + } } } } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index e35ca46..f995dee 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -896,10 +896,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // Traffic occurring on the base interface is always counted for // both total usage and UID details. final String baseIface = state.linkProperties.getInterfaceName(); - findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); - findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); - if (isMobile) { - mobileIfaces.add(baseIface); + if (baseIface != null) { + findOrCreateNetworkIdentitySet(mActiveIfaces, baseIface).add(ident); + findOrCreateNetworkIdentitySet(mActiveUidIfaces, baseIface).add(ident); + if (isMobile) { + mobileIfaces.add(baseIface); + } } // Traffic occurring on stacked interfaces is usually clatd, @@ -909,15 +911,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks(); for (LinkProperties stackedLink : stackedLinks) { final String stackedIface = stackedLink.getInterfaceName(); - findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident); - if (isMobile) { - mobileIfaces.add(stackedIface); + if (stackedIface != null) { + findOrCreateNetworkIdentitySet(mActiveUidIfaces, stackedIface).add(ident); + if (isMobile) { + mobileIfaces.add(stackedIface); + } } } } } - mobileIfaces.remove(null); mMobileIfaces = mobileIfaces.toArray(new String[mobileIfaces.size()]); } diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index a06daf6..189131c 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -51,8 +51,9 @@ public class ConditionProviders extends ManagedServices { = new ArrayMap<IBinder, IConditionListener>(); private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>(); private final CountdownConditionProvider mCountdown = new CountdownConditionProvider(); + private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider(); - private Uri mExitConditionId; + private Condition mExitCondition; private ComponentName mExitConditionComponent; public ConditionProviders(Context context, Handler handler, @@ -97,6 +98,7 @@ public class ConditionProviders extends ManagedServices { } } mCountdown.dump(pw, filter); + mDowntime.dump(pw, filter); } @Override @@ -110,6 +112,10 @@ public class ConditionProviders extends ManagedServices { mCountdown.attachBase(mContext); registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT, UserHandle.USER_OWNER); + mDowntime.attachBase(mContext); + registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT, + UserHandle.USER_OWNER); + mDowntime.setCallback(new DowntimeCallback()); } @Override @@ -125,7 +131,7 @@ public class ConditionProviders extends ManagedServices { if (info.component.equals(mExitConditionComponent)) { // ensure record exists, we'll wire it up and subscribe below final ConditionRecord manualRecord = - getRecordLocked(mExitConditionId, mExitConditionComponent); + getRecordLocked(mExitCondition.id, mExitConditionComponent); manualRecord.isManual = true; } final int N = mRecords.size(); @@ -149,11 +155,11 @@ public class ConditionProviders extends ManagedServices { if (!r.component.equals(removed.component)) continue; if (r.isManual) { // removing the current manual condition, exit zen - mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved"); } if (r.isAutomatic) { // removing an automatic condition, exit zen - mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved"); } mRecords.remove(i); } @@ -249,7 +255,8 @@ public class ConditionProviders extends ManagedServices { } else if (DEBUG) { Slog.d(TAG, "Exit zen: manual condition false: " + c); } - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, + "manualConditionExit"); unsubscribeLocked(r); r.isManual = false; } @@ -263,33 +270,46 @@ public class ConditionProviders extends ManagedServices { } else if (DEBUG) { Slog.d(TAG, "Exit zen: automatic condition false: " + c); } - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, + "automaticConditionExit"); } else if (c.state == Condition.STATE_TRUE) { Slog.d(TAG, "Enter zen: automatic condition true: " + c); - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, + "automaticConditionEnter"); } } } } } - public void setZenModeCondition(Uri conditionId, String reason) { - if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId); + public void setZenModeCondition(Condition condition, String reason) { + if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition); synchronized(mMutex) { ComponentName conditionComponent = null; - if (ZenModeConfig.isValidCountdownConditionId(conditionId)) { - // constructed by the client, make sure the record exists... - final ConditionRecord r = getRecordLocked(conditionId, - CountdownConditionProvider.COMPONENT); - if (r.info == null) { - // ... and is associated with the in-process service - r.info = checkServiceTokenLocked(mCountdown.asInterface()); + if (condition != null) { + if (ZenModeConfig.isValidCountdownConditionId(condition.id)) { + // constructed by the client, make sure the record exists... + final ConditionRecord r = getRecordLocked(condition.id, + CountdownConditionProvider.COMPONENT); + if (r.info == null) { + // ... and is associated with the in-process service + r.info = checkServiceTokenLocked(mCountdown.asInterface()); + } + } + if (ZenModeConfig.isValidDowntimeConditionId(condition.id)) { + // constructed by the client, make sure the record exists... + final ConditionRecord r = getRecordLocked(condition.id, + DowntimeConditionProvider.COMPONENT); + if (r.info == null) { + // ... and is associated with the in-process service + r.info = checkServiceTokenLocked(mDowntime.asInterface()); + } } } final int N = mRecords.size(); for (int i = 0; i < N; i++) { final ConditionRecord r = mRecords.get(i); - final boolean idEqual = r.id.equals(conditionId); + final boolean idEqual = condition != null && r.id.equals(condition.id); if (r.isManual && !idEqual) { // was previous manual condition, unsubscribe unsubscribeLocked(r); @@ -303,10 +323,10 @@ public class ConditionProviders extends ManagedServices { conditionComponent = r.component; } } - if (!Objects.equals(mExitConditionId, conditionId)) { - mExitConditionId = conditionId; + if (!Objects.equals(mExitCondition, condition)) { + mExitCondition = condition; mExitConditionComponent = conditionComponent; - ZenLog.traceExitCondition(mExitConditionId, mExitConditionComponent, reason); + ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason); saveZenConfigLocked(); } } @@ -318,6 +338,7 @@ public class ConditionProviders extends ManagedServices { RemoteException re = null; if (provider != null) { try { + Slog.d(TAG, "Subscribing to " + r.id + " with " + provider); provider.onSubscribe(r.id); } catch (RemoteException e) { Slog.w(TAG, "Error subscribing to " + r, e); @@ -436,12 +457,13 @@ public class ConditionProviders extends ManagedServices { return; } synchronized (mMutex) { - final boolean changingExit = !Objects.equals(mExitConditionId, config.exitConditionId); - mExitConditionId = config.exitConditionId; + final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition); + mExitCondition = config.exitCondition; mExitConditionComponent = config.exitConditionComponent; if (changingExit) { - ZenLog.traceExitCondition(mExitConditionId, mExitConditionComponent, "config"); + ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config"); } + mDowntime.setConfig(config); if (config.conditionComponents == null || config.conditionIds == null || config.conditionComponents.length != config.conditionIds.length) { if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions"); @@ -488,7 +510,7 @@ public class ConditionProviders extends ManagedServices { config.conditionIds[i] = r.id; } } - config.exitConditionId = mExitConditionId; + config.exitCondition = mExitCondition; config.exitConditionComponent = mExitConditionComponent; if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config); mZenModeHelper.setConfig(config); @@ -510,6 +532,26 @@ public class ConditionProviders extends ManagedServices { } } + private class DowntimeCallback implements DowntimeConditionProvider.Callback { + @Override + public void onDowntimeChanged(boolean inDowntime) { + final int mode = mZenModeHelper.getZenMode(); + final ZenModeConfig config = mZenModeHelper.getConfig(); + // enter downtime + if (inDowntime && mode == Global.ZEN_MODE_OFF && config != null) { + final Condition condition = mDowntime.createCondition(config.toDowntimeInfo(), + Condition.STATE_TRUE); + mZenModeHelper.setZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtimeEnter"); + setZenModeCondition(condition, "downtime"); + } + // exit downtime + if (!inDowntime && mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + && mDowntime.isDowntimeCondition(mExitCondition)) { + mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit"); + } + } + } + private static class ConditionRecord { public final Uri id; public final ComponentName component; diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java index aaf7cfc..37aacaa 100644 --- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java +++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java @@ -29,6 +29,7 @@ import android.service.notification.ConditionProviderService; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.text.format.DateUtils; +import android.util.Log; import android.util.Slog; import com.android.server.notification.NotificationManagerService.DumpFilter; @@ -38,8 +39,8 @@ import java.util.Date; /** Built-in zen condition provider for simple time-based conditions */ public class CountdownConditionProvider extends ConditionProviderService { - private static final String TAG = "CountdownConditionProvider"; - private static final boolean DEBUG = false; + private static final String TAG = "CountdownConditions"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final ComponentName COMPONENT = new ComponentName("android", CountdownConditionProvider.class.getName()); diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java new file mode 100644 index 0000000..317ebef --- /dev/null +++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java @@ -0,0 +1,289 @@ +/** + * Copyright (c) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.service.notification.Condition; +import android.service.notification.ConditionProviderService; +import android.service.notification.IConditionProvider; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.DowntimeInfo; +import android.text.format.DateFormat; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.R; +import com.android.server.notification.NotificationManagerService.DumpFilter; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.Objects; + +/** Built-in zen condition provider for managing downtime */ +public class DowntimeConditionProvider extends ConditionProviderService { + private static final String TAG = "DowntimeConditions"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + public static final ComponentName COMPONENT = + new ComponentName("android", DowntimeConditionProvider.class.getName()); + + private static final String ENTER_ACTION = TAG + ".enter"; + private static final int ENTER_CODE = 100; + private static final String EXIT_ACTION = TAG + ".exit"; + private static final int EXIT_CODE = 101; + private static final String EXTRA_TIME = "time"; + + private final Calendar mCalendar = Calendar.getInstance(); + private final Context mContext = this; + private final ArraySet<Integer> mDays = new ArraySet<Integer>(); + + private boolean mConnected; + private boolean mInDowntime; + private ZenModeConfig mConfig; + private Callback mCallback; + + public DowntimeConditionProvider() { + if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()"); + } + + public void dump(PrintWriter pw, DumpFilter filter) { + pw.println(" DowntimeConditionProvider:"); + pw.print(" mConnected="); pw.println(mConnected); + pw.print(" mInDowntime="); pw.println(mInDowntime); + } + + public void attachBase(Context base) { + attachBaseContext(base); + } + + public IConditionProvider asInterface() { + return (IConditionProvider) onBind(null); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public void onConnected() { + if (DEBUG) Slog.d(TAG, "onConnected"); + mConnected = true; + final IntentFilter filter = new IntentFilter(); + filter.addAction(ENTER_ACTION); + filter.addAction(EXIT_ACTION); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + mContext.registerReceiver(mReceiver, filter); + init(); + } + + @Override + public void onDestroy() { + if (DEBUG) Slog.d(TAG, "onDestroy"); + mConnected = false; + } + + @Override + public void onRequestConditions(int relevance) { + if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); + if ((relevance & Condition.FLAG_RELEVANT_NOW) != 0) { + if (mInDowntime && mConfig != null) { + notifyCondition(createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE)); + } + } + } + + @Override + public void onSubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId); + final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId); + if (downtime != null && mConfig != null) { + final int state = mConfig.toDowntimeInfo().equals(downtime) && mInDowntime + ? Condition.STATE_TRUE : Condition.STATE_FALSE; + if (DEBUG) Slog.d(TAG, "notify condition state: " + Condition.stateToString(state)); + notifyCondition(createCondition(downtime, state)); + } + } + + @Override + public void onUnsubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId); + } + + public void setConfig(ZenModeConfig config) { + if (Objects.equals(mConfig, config)) return; + if (DEBUG) Slog.d(TAG, "setConfig"); + mConfig = config; + if (mConnected) { + init(); + } + } + + public boolean isInDowntime() { + return mInDowntime; + } + + public Condition createCondition(DowntimeInfo downtime, int state) { + if (downtime == null) return null; + final Uri id = ZenModeConfig.toDowntimeConditionId(downtime); + final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma"; + final Locale locale = Locale.getDefault(); + final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton); + final long time = getTime(System.currentTimeMillis(), downtime.endHour, downtime.endMinute); + final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(time)); + final String summary = mContext.getString(R.string.downtime_condition_summary, formatted); + return new Condition(id, summary, "", "", 0, state, Condition.FLAG_RELEVANT_NOW); + } + + public boolean isDowntimeCondition(Condition condition) { + return condition != null && ZenModeConfig.isValidDowntimeConditionId(condition.id); + } + + private void init() { + updateDays(); + reevaluateDowntime(); + updateAlarms(); + } + + private void updateDays() { + mDays.clear(); + if (mConfig != null) { + final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode); + for (int i = 0; days != null && i < days.length; i++) { + mDays.add(days[i]); + } + } + } + + private boolean isInDowntime(long time) { + if (mConfig == null || mDays.size() == 0) return false; + final long start = getTime(time, mConfig.sleepStartHour, mConfig.sleepStartMinute); + long end = getTime(time, mConfig.sleepEndHour, mConfig.sleepEndMinute); + if (start == end) return false; + if (end < start) { + end = addDays(end, 1); + } + return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end); + } + + private boolean isInDowntime(int daysOffset, long time, long start, long end) { + final int day = ((getDayOfWeek(time) + daysOffset - 1) % Calendar.SATURDAY) + 1; + start = addDays(start, daysOffset); + end = addDays(end, daysOffset); + return mDays.contains(day) && time >= start && time < end; + } + + private void reevaluateDowntime() { + final boolean inDowntime = isInDowntime(System.currentTimeMillis()); + if (DEBUG) Slog.d(TAG, "inDowntime=" + inDowntime); + if (inDowntime == mInDowntime) return; + Slog.i(TAG, (inDowntime ? "Entering" : "Exiting" ) + " downtime"); + mInDowntime = inDowntime; + ZenLog.traceDowntime(mInDowntime, getDayOfWeek(System.currentTimeMillis()), mDays); + fireDowntimeChanged(); + } + + private void fireDowntimeChanged() { + if (mCallback != null) { + mCallback.onDowntimeChanged(mInDowntime); + } + } + + private void updateAlarms() { + if (mConfig == null) return; + updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute); + updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute); + } + + private int getDayOfWeek(long time) { + mCalendar.setTimeInMillis(time); + return mCalendar.get(Calendar.DAY_OF_WEEK); + } + + private long getTime(long millis, int hour, int min) { + mCalendar.setTimeInMillis(millis); + mCalendar.set(Calendar.HOUR_OF_DAY, hour); + mCalendar.set(Calendar.MINUTE, min); + mCalendar.set(Calendar.SECOND, 0); + mCalendar.set(Calendar.MILLISECOND, 0); + return mCalendar.getTimeInMillis(); + } + + private long addDays(long time, int days) { + mCalendar.setTimeInMillis(time); + mCalendar.add(Calendar.DATE, days); + return mCalendar.getTimeInMillis(); + } + + private void updateAlarm(String action, int requestCode, int hr, int min) { + final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + final long now = System.currentTimeMillis(); + mCalendar.setTimeInMillis(now); + mCalendar.set(Calendar.HOUR_OF_DAY, hr); + mCalendar.set(Calendar.MINUTE, min); + mCalendar.set(Calendar.SECOND, 0); + mCalendar.set(Calendar.MILLISECOND, 0); + long time = mCalendar.getTimeInMillis(); + if (time <= now) { + time = addDays(time, 1); + } + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, + new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); + alarms.cancel(pendingIntent); + if (mConfig.sleepMode != null) { + if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", + action, ts(time), time - now, ts(now))); + alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); + } + } + + private static String ts(long time) { + return new Date(time) + " (" + time + ")"; + } + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final long now = System.currentTimeMillis(); + if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) { + final long schTime = intent.getLongExtra(EXTRA_TIME, 0); + if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", + action, ts(schTime), ts(now), now - schTime)); + } else { + if (DEBUG) Slog.d(TAG, action + " fired at " + now); + } + reevaluateDowntime(); + updateAlarms(); + } + }; + + public interface Callback { + void onDowntimeChanged(boolean inDowntime); + } +} diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 36be21f..f647037 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -41,6 +41,7 @@ import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -64,7 +65,7 @@ import java.util.Set; */ abstract public class ManagedServices { protected final String TAG = getClass().getSimpleName(); - protected static final boolean DEBUG = true; + protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String ENABLED_SERVICES_SEPARATOR = ":"; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index d6afe68..f2ac963 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1351,11 +1351,11 @@ public class NotificationManagerService extends SystemService { } @Override - public void setZenModeCondition(Uri conditionId) { + public void setZenModeCondition(Condition condition) { enforceSystemOrSystemUI("INotificationManager.setZenModeCondition"); final long identity = Binder.clearCallingIdentity(); try { - mConditionProviders.setZenModeCondition(conditionId, "binderCall"); + mConditionProviders.setZenModeCondition(condition, "binderCall"); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index b22ed2d..525f5f8 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -22,8 +22,10 @@ import android.net.Uri; import android.os.Build; import android.os.RemoteException; import android.provider.Settings.Global; +import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; +import android.util.ArraySet; import android.util.Slog; import java.io.PrintWriter; @@ -52,13 +54,14 @@ public class ZenLog { private static final int TYPE_ALLOW_DISABLE = 2; private static final int TYPE_SET_RINGER_MODE = 3; private static final int TYPE_DOWNTIME = 4; - private static final int TYPE_ZEN_MODE = 5; - private static final int TYPE_EXIT_CONDITION = 6; - private static final int TYPE_SUBSCRIBE = 7; - private static final int TYPE_UNSUBSCRIBE = 8; - private static final int TYPE_CONFIG = 9; - private static final int TYPE_FOLLOW_RINGER_MODE = 10; - private static final int TYPE_NOT_INTERCEPTED = 11; + private static final int TYPE_SET_ZEN_MODE = 5; + private static final int TYPE_UPDATE_ZEN_MODE = 6; + private static final int TYPE_EXIT_CONDITION = 7; + private static final int TYPE_SUBSCRIBE = 8; + private static final int TYPE_UNSUBSCRIBE = 9; + private static final int TYPE_CONFIG = 10; + private static final int TYPE_FOLLOW_RINGER_MODE = 11; + private static final int TYPE_NOT_INTERCEPTED = 12; private static int sNext; private static int sSize; @@ -82,17 +85,20 @@ public class ZenLog { append(TYPE_SET_RINGER_MODE, ringerModeToString(ringerMode)); } - public static void traceDowntime(boolean enter, int day, int[] days) { - append(TYPE_DOWNTIME, enter + ",day=" + day + ",days=" + (days != null ? Arrays.asList(days) - : null)); + public static void traceDowntime(boolean inDowntime, int day, ArraySet<Integer> days) { + append(TYPE_DOWNTIME, inDowntime + ",day=" + day + ",days=" + days); + } + + public static void traceSetZenMode(int mode, String reason) { + append(TYPE_SET_ZEN_MODE, zenModeToString(mode) + "," + reason); } public static void traceUpdateZenMode(int fromMode, int toMode) { - append(TYPE_ZEN_MODE, zenModeToString(fromMode) + " -> " + zenModeToString(toMode)); + append(TYPE_UPDATE_ZEN_MODE, zenModeToString(fromMode) + " -> " + zenModeToString(toMode)); } - public static void traceExitCondition(Uri id, ComponentName component, String reason) { - append(TYPE_EXIT_CONDITION, id + "," + componentToString(component) + "," + reason); + public static void traceExitCondition(Condition c, ComponentName component, String reason) { + append(TYPE_EXIT_CONDITION, c + "," + componentToString(component) + "," + reason); } public static void traceSubscribe(Uri uri, IConditionProvider provider, RemoteException e) { @@ -122,7 +128,8 @@ public class ZenLog { case TYPE_ALLOW_DISABLE: return "allow_disable"; case TYPE_SET_RINGER_MODE: return "set_ringer_mode"; case TYPE_DOWNTIME: return "downtime"; - case TYPE_ZEN_MODE: return "zen_mode"; + case TYPE_SET_ZEN_MODE: return "set_zen_mode"; + case TYPE_UPDATE_ZEN_MODE: return "update_zen_mode"; case TYPE_EXIT_CONDITION: return "exit_condition"; case TYPE_SUBSCRIBE: return "subscribe"; case TYPE_UNSUBSCRIBE: return "unsubscribe"; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 9282283..758f334 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -20,10 +20,8 @@ import static android.media.AudioAttributes.USAGE_ALARM; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.media.AudioAttributes.USAGE_UNKNOWN; -import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.Notification; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -44,6 +42,7 @@ import android.provider.Settings.Secure; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.telecomm.TelecommManager; +import android.util.Log; import android.util.Slog; import com.android.internal.R; @@ -57,8 +56,6 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; import java.util.Objects; /** @@ -66,12 +63,7 @@ import java.util.Objects; */ public class ZenModeHelper { private static final String TAG = "ZenModeHelper"; - - private static final String ACTION_ENTER_ZEN = "enter_zen"; - private static final int REQUEST_CODE_ENTER = 100; - private static final String ACTION_EXIT_ZEN = "exit_zen"; - private static final int REQUEST_CODE_EXIT = 101; - private static final String EXTRA_TIME = "time"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final Handler mHandler; @@ -96,10 +88,8 @@ public class ZenModeHelper { mSettingsObserver.observe(); final IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_ENTER_ZEN); - filter.addAction(ACTION_EXIT_ZEN); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - mContext.registerReceiver(new ZenBroadcastReceiver(), filter); + mContext.registerReceiver(mReceiver, filter); } public static ZenModeConfig readDefaultConfig(Resources resources) { @@ -156,7 +146,7 @@ public class ZenModeHelper { public void requestFromListener(int hints) { final int newZen = zenFromListenerHint(hints, -1); if (newZen != -1) { - setZenMode(newZen); + setZenMode(newZen, "listener"); } } @@ -179,24 +169,19 @@ public class ZenModeHelper { return false; } } - // audience has veto power over all following rules - if (!audienceMatches(record)) { - ZenLog.traceIntercepted(record, "!audienceMatches"); - return true; - } if (isCall(record)) { if (!mConfig.allowCalls) { ZenLog.traceIntercepted(record, "!allowCalls"); return true; } - return false; + return shouldInterceptAudience(record); } if (isMessage(record)) { if (!mConfig.allowMessages) { ZenLog.traceIntercepted(record, "!allowMessages"); return true; } - return false; + return shouldInterceptAudience(record); } ZenLog.traceIntercepted(record, "!allowed"); return true; @@ -204,11 +189,20 @@ public class ZenModeHelper { return false; } + private boolean shouldInterceptAudience(NotificationRecord record) { + if (!audienceMatches(record)) { + ZenLog.traceIntercepted(record, "!audienceMatches"); + return true; + } + return false; + } + public int getZenMode() { return mZenMode; } - public void setZenMode(int zenModeValue) { + public void setZenMode(int zenModeValue, String reason) { + ZenLog.traceSetZenMode(zenModeValue, reason); Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue); } @@ -216,9 +210,6 @@ public class ZenModeHelper { final int mode = Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF); if (mode != mZenMode) { - Slog.d(TAG, String.format("updateZenMode: %s -> %s", - Global.zenModeToString(mZenMode), - Global.zenModeToString(mode))); ZenLog.traceUpdateZenMode(mZenMode, mode); } mZenMode = mode; @@ -255,12 +246,12 @@ public class ZenModeHelper { if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { if (ringerMode != AudioManager.RINGER_MODE_SILENT) { mPreviousRingerMode = ringerMode; - Slog.d(TAG, "Silencing ringer"); + if (DEBUG) Slog.d(TAG, "Silencing ringer"); forcedRingerMode = AudioManager.RINGER_MODE_SILENT; } } else { if (ringerMode == AudioManager.RINGER_MODE_SILENT) { - Slog.d(TAG, "Unsilencing ringer"); + if (DEBUG) Slog.d(TAG, "Unsilencing ringer"); forcedRingerMode = mPreviousRingerMode != -1 ? mPreviousRingerMode : AudioManager.RINGER_MODE_NORMAL; mPreviousRingerMode = -1; @@ -318,7 +309,6 @@ public class ZenModeHelper { dispatchOnConfigChanged(); final String val = Integer.toString(mConfig.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); - updateAlarms(); updateZenMode(); return true; } @@ -339,7 +329,7 @@ public class ZenModeHelper { } if (newZen != -1) { ZenLog.traceFollowRingerMode(ringerMode, mZenMode, newZen); - setZenMode(newZen); + setZenMode(newZen, "ringerMode"); } } } @@ -377,7 +367,7 @@ public class ZenModeHelper { final TelecommManager telecomm = (TelecommManager) mContext.getSystemService(Context.TELECOMM_SERVICE); mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; - Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); + if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); } return pkg != null && mDefaultPhoneApp != null && pkg.equals(mDefaultPhoneApp.getPackageName()); @@ -409,40 +399,6 @@ public class ZenModeHelper { } } - private void updateAlarms() { - updateAlarm(ACTION_ENTER_ZEN, REQUEST_CODE_ENTER, - mConfig.sleepStartHour, mConfig.sleepStartMinute); - updateAlarm(ACTION_EXIT_ZEN, REQUEST_CODE_EXIT, - mConfig.sleepEndHour, mConfig.sleepEndMinute); - } - - private void updateAlarm(String action, int requestCode, int hr, int min) { - final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final long now = System.currentTimeMillis(); - final Calendar c = Calendar.getInstance(); - c.setTimeInMillis(now); - c.set(Calendar.HOUR_OF_DAY, hr); - c.set(Calendar.MINUTE, min); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - if (c.getTimeInMillis() <= now) { - c.add(Calendar.DATE, 1); - } - final long time = c.getTimeInMillis(); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, - new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); - alarms.cancel(pendingIntent); - if (mConfig.sleepMode != null) { - Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", - action, ts(time), time - now, ts(now))); - alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); - } - } - - private static String ts(long time) { - return new Date(time) + " (" + time + ")"; - } - private final Runnable mRingerModeChanged = new Runnable() { @Override public void run() { @@ -475,47 +431,12 @@ public class ZenModeHelper { } } - private class ZenBroadcastReceiver extends BroadcastReceiver { - private final Calendar mCalendar = Calendar.getInstance(); - + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (ACTION_ENTER_ZEN.equals(intent.getAction())) { - setZenMode(intent, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); - } else if (ACTION_EXIT_ZEN.equals(intent.getAction())) { - setZenMode(intent, Global.ZEN_MODE_OFF); - } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) { - mHandler.post(mRingerModeChanged); - } - } - - private void setZenMode(Intent intent, int zenModeValue) { - final long schTime = intent.getLongExtra(EXTRA_TIME, 0); - final long now = System.currentTimeMillis(); - Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", - intent.getAction(), ts(schTime), ts(now), now - schTime)); - - final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode); - boolean enter = false; - final int day = getDayOfWeek(schTime); - if (days != null) { - for (int i = 0; i < days.length; i++) { - if (days[i] == day) { - enter = true; - ZenModeHelper.this.setZenMode(zenModeValue); - break; - } - } - } - ZenLog.traceDowntime(enter, day, days); - updateAlarms(); + mHandler.post(mRingerModeChanged); } - - private int getDayOfWeek(long time) { - mCalendar.setTimeInMillis(time); - return mCalendar.get(Calendar.DAY_OF_WEEK); - } - } + }; public static class Callback { void onConfigChanged() {} diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index b261ef5..d1e03ec 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -16,33 +16,23 @@ package com.android.server.pm; -import com.android.server.SystemService; - import android.content.Context; import android.content.pm.PackageStats; -import android.net.LocalSocket; -import android.net.LocalSocketAddress; +import android.os.Build; import android.util.Slog; +import dalvik.system.VMRuntime; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; +import com.android.internal.os.InstallerConnection; +import com.android.server.SystemService; public final class Installer extends SystemService { private static final String TAG = "Installer"; - private static final boolean LOCAL_DEBUG = false; - - InputStream mIn; - OutputStream mOut; - LocalSocket mSocket; - - byte buf[] = new byte[1024]; - int buflen = 0; + private final InstallerConnection mInstaller; public Installer(Context context) { super(context); + mInstaller = new InstallerConnection(); } @Override @@ -51,154 +41,6 @@ public final class Installer extends SystemService { ping(); } - private boolean connect() { - if (mSocket != null) { - return true; - } - Slog.i(TAG, "connecting..."); - try { - mSocket = new LocalSocket(); - - LocalSocketAddress address = new LocalSocketAddress("installd", - LocalSocketAddress.Namespace.RESERVED); - - mSocket.connect(address); - - mIn = mSocket.getInputStream(); - mOut = mSocket.getOutputStream(); - } catch (IOException ex) { - disconnect(); - return false; - } - return true; - } - - private void disconnect() { - Slog.i(TAG, "disconnecting..."); - try { - if (mSocket != null) - mSocket.close(); - } catch (IOException ex) { - } - try { - if (mIn != null) - mIn.close(); - } catch (IOException ex) { - } - try { - if (mOut != null) - mOut.close(); - } catch (IOException ex) { - } - mSocket = null; - mIn = null; - mOut = null; - } - - private boolean readBytes(byte buffer[], int len) { - int off = 0, count; - if (len < 0) - return false; - while (off != len) { - try { - count = mIn.read(buffer, off, len - off); - if (count <= 0) { - Slog.e(TAG, "read error " + count); - break; - } - off += count; - } catch (IOException ex) { - Slog.e(TAG, "read exception"); - break; - } - } - if (LOCAL_DEBUG) { - Slog.i(TAG, "read " + len + " bytes"); - } - if (off == len) - return true; - disconnect(); - return false; - } - - private boolean readReply() { - int len; - buflen = 0; - if (!readBytes(buf, 2)) - return false; - len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); - if ((len < 1) || (len > 1024)) { - Slog.e(TAG, "invalid reply length (" + len + ")"); - disconnect(); - return false; - } - if (!readBytes(buf, len)) - return false; - buflen = len; - return true; - } - - private boolean writeCommand(String _cmd) { - byte[] cmd = _cmd.getBytes(); - int len = cmd.length; - if ((len < 1) || (len > 1024)) - return false; - buf[0] = (byte) (len & 0xff); - buf[1] = (byte) ((len >> 8) & 0xff); - try { - mOut.write(buf, 0, 2); - mOut.write(cmd, 0, len); - } catch (IOException ex) { - Slog.e(TAG, "write error"); - disconnect(); - return false; - } - return true; - } - - private synchronized String transaction(String cmd) { - if (!connect()) { - Slog.e(TAG, "connection failed"); - return "-1"; - } - - if (!writeCommand(cmd)) { - /* - * If installd died and restarted in the background (unlikely but - * possible) we'll fail on the next write (this one). Try to - * reconnect and write the command one more time before giving up. - */ - Slog.e(TAG, "write command failed? reconnect!"); - if (!connect() || !writeCommand(cmd)) { - return "-1"; - } - } - if (LOCAL_DEBUG) { - Slog.i(TAG, "send: '" + cmd + "'"); - } - if (readReply()) { - String s = new String(buf, 0, buflen); - if (LOCAL_DEBUG) { - Slog.i(TAG, "recv: '" + s + "'"); - } - return s; - } else { - if (LOCAL_DEBUG) { - Slog.i(TAG, "fail"); - } - return "-1"; - } - } - - private int execute(String cmd) { - String res = transaction(cmd); - try { - return Integer.parseInt(res); - } catch (NumberFormatException ex) { - return -1; - } - } - public int install(String name, int uid, int gid, String seinfo) { StringBuilder builder = new StringBuilder("install"); builder.append(' '); @@ -209,11 +51,16 @@ public final class Installer extends SystemService { builder.append(gid); builder.append(' '); builder.append(seinfo != null ? seinfo : "!"); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName, String instructionSet) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + StringBuilder builder = new StringBuilder("patchoat"); builder.append(' '); builder.append(apkPath); @@ -224,37 +71,34 @@ public final class Installer extends SystemService { builder.append(pkgName); builder.append(' '); builder.append(instructionSet); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) { - StringBuilder builder = new StringBuilder("patchoat"); - builder.append(' '); - builder.append(apkPath); - builder.append(' '); - builder.append(uid); - builder.append(isPublic ? " 1" : " 0"); - builder.append(" *"); // No pkgName arg present - builder.append(' '); - builder.append(instructionSet); - return execute(builder.toString()); + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + + return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet); } public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) { - StringBuilder builder = new StringBuilder("dexopt"); - builder.append(' '); - builder.append(apkPath); - builder.append(' '); - builder.append(uid); - builder.append(isPublic ? " 1" : " 0"); - builder.append(" *"); // No pkgName arg present - builder.append(' '); - builder.append(instructionSet); - return execute(builder.toString()); + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + + return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet); } public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName, String instructionSet) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + StringBuilder builder = new StringBuilder("dexopt"); builder.append(' '); builder.append(apkPath); @@ -265,7 +109,7 @@ public final class Installer extends SystemService { builder.append(pkgName); builder.append(' '); builder.append(instructionSet); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int idmap(String targetApkPath, String overlayApkPath, int uid) { @@ -276,10 +120,15 @@ public final class Installer extends SystemService { builder.append(overlayApkPath); builder.append(' '); builder.append(uid); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int movedex(String srcPath, String dstPath, String instructionSet) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + StringBuilder builder = new StringBuilder("movedex"); builder.append(' '); builder.append(srcPath); @@ -287,16 +136,21 @@ public final class Installer extends SystemService { builder.append(dstPath); builder.append(' '); builder.append(instructionSet); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int rmdex(String codePath, String instructionSet) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + StringBuilder builder = new StringBuilder("rmdex"); builder.append(' '); builder.append(codePath); builder.append(' '); builder.append(instructionSet); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int remove(String name, int userId) { @@ -305,7 +159,7 @@ public final class Installer extends SystemService { builder.append(name); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int rename(String oldname, String newname) { @@ -314,7 +168,7 @@ public final class Installer extends SystemService { builder.append(oldname); builder.append(' '); builder.append(newname); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int fixUid(String name, int uid, int gid) { @@ -325,7 +179,7 @@ public final class Installer extends SystemService { builder.append(uid); builder.append(' '); builder.append(gid); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int deleteCacheFiles(String name, int userId) { @@ -334,7 +188,7 @@ public final class Installer extends SystemService { builder.append(name); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int deleteCodeCacheFiles(String name, int userId) { @@ -343,7 +197,7 @@ public final class Installer extends SystemService { builder.append(name); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int createUserData(String name, int uid, int userId, String seinfo) { @@ -356,21 +210,21 @@ public final class Installer extends SystemService { builder.append(userId); builder.append(' '); builder.append(seinfo != null ? seinfo : "!"); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int createUserConfig(int userId) { StringBuilder builder = new StringBuilder("mkuserconfig"); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int removeUserDataDirs(int userId) { StringBuilder builder = new StringBuilder("rmuser"); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int clearUserData(String name, int userId) { @@ -379,11 +233,11 @@ public final class Installer extends SystemService { builder.append(name); builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public boolean ping() { - if (execute("ping") < 0) { + if (mInstaller.execute("ping") < 0) { return false; } else { return true; @@ -391,18 +245,25 @@ public final class Installer extends SystemService { } public int pruneDexCache(String cacheSubDir) { - return execute("prunedexcache " + cacheSubDir); + return mInstaller.execute("prunedexcache " + cacheSubDir); } public int freeCache(long freeStorageSize) { StringBuilder builder = new StringBuilder("freecache"); builder.append(' '); builder.append(String.valueOf(freeStorageSize)); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets, PackageStats pStats) { + for (String instructionSet : instructionSets) { + if (!isValidInstructionSet(instructionSet)) { + Slog.e(TAG, "Invalid instruction set: " + instructionSet); + return -1; + } + } + StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); builder.append(pkgName); @@ -423,7 +284,7 @@ public final class Installer extends SystemService { // just the primary. builder.append(instructionSets[0]); - String s = transaction(builder.toString()); + String s = mInstaller.transact(builder.toString()); String res[] = s.split(" "); if ((res == null) || (res.length != 5)) { @@ -441,7 +302,7 @@ public final class Installer extends SystemService { } public int moveFiles() { - return execute("movefiles"); + return mInstaller.execute("movefiles"); } /** @@ -467,7 +328,7 @@ public final class Installer extends SystemService { builder.append(' '); builder.append(userId); - return execute(builder.toString()); + return mInstaller.execute(builder.toString()); } public boolean restoreconData(String pkgName, String seinfo, int uid) { @@ -478,6 +339,23 @@ public final class Installer extends SystemService { builder.append(seinfo != null ? seinfo : "!"); builder.append(' '); builder.append(uid); - return (execute(builder.toString()) == 0); + return (mInstaller.execute(builder.toString()) == 0); + } + + /** + * Returns true iff. {@code instructionSet} is a valid instruction set. + */ + private static boolean isValidInstructionSet(String instructionSet) { + if (instructionSet == null) { + return false; + } + + for (String abi : Build.SUPPORTED_ABIS) { + if (instructionSet.equals(VMRuntime.getInstructionSet(abi))) { + return true; + } + } + + return false; } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 89bd1d4..304441c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1394,16 +1394,27 @@ public class PackageManagerService extends IPackageManager.Stub { * list of process files because dexopt will have been run * if necessary during zygote startup. */ - String bootClassPath = System.getProperty("java.boot.class.path"); + final String bootClassPath = System.getenv("BOOTCLASSPATH"); + final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH"); + if (bootClassPath != null) { - String[] paths = splitString(bootClassPath, ':'); - for (int i=0; i<paths.length; i++) { - alreadyDexOpted.add(paths[i]); + String[] bootClassPathElements = splitString(bootClassPath, ':'); + for (String element : bootClassPathElements) { + alreadyDexOpted.add(element); } } else { Slog.w(TAG, "No BOOTCLASSPATH found!"); } + if (systemServerClassPath != null) { + String[] systemServerClassPathElements = splitString(systemServerClassPath, ':'); + for (String element : systemServerClassPathElements) { + alreadyDexOpted.add(element); + } + } else { + Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!"); + } + boolean didDexOptLibraryOrTool = false; final List<String> allInstructionSets = getAllInstructionSets(); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 39c6e0e..db19285 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -58,8 +58,6 @@ import android.os.SystemProperties; import android.os.SystemService; import android.os.UserHandle; import android.os.WorkSource; -import android.os.Parcel; -import android.os.ServiceManager; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.util.EventLog; @@ -709,7 +707,6 @@ public final class PowerManagerService extends com.android.server.SystemService if (mLowPowerModeEnabled != lowPowerModeEnabled) { mLowPowerModeEnabled = lowPowerModeEnabled; powerHintInternal(POWER_HINT_LOW_POWER_MODE, lowPowerModeEnabled ? 1 : 0); - setSurfaceFlingerLowPowerMode(lowPowerModeEnabled ? 1 : 0); mLowPowerModeEnabled = lowPowerModeEnabled; BackgroundThread.getHandler().post(new Runnable() { @Override @@ -2198,21 +2195,6 @@ public final class PowerManagerService extends com.android.server.SystemService nativeSendPowerHint(hintId, data); } - private static void setSurfaceFlingerLowPowerMode(int enabled) { - try { - final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); - if (flinger != null) { - final Parcel data = Parcel.obtain(); - data.writeInterfaceToken("android.ui.ISurfaceComposer"); - data.writeInt(enabled); - flinger.transact(1016, data, null, 0); - data.recycle(); - } - } catch (RemoteException ex) { - Slog.e(TAG, "Failed to reduce refresh rate", ex); - } - } - /** * Low-level function turn the device off immediately, without trying * to be clean. Most people should use {@link ShutdownThread} for a clean shutdown. diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 3fcd067..b2575e6 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -252,6 +252,15 @@ class AppWindowToken extends WindowToken { return false; } + void removeAllWindows() { + for (int winNdx = allAppWindows.size() - 1; winNdx >= 0; --winNdx) { + WindowState win = allAppWindows.get(winNdx); + if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) Slog.w(WindowManagerService.TAG, + "removeAllWindows: removing win=" + win); + win.mService.removeWindowLocked(win.mSession, win); + } + } + @Override void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index dfb1200..238c77e 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -155,7 +155,7 @@ public class TaskStack { final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows; for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { final WindowStateAnimator winAnimator = windows.get(winNdx).mWinAnimator; - if (winAnimator.isAnimating() && !winAnimator.isDummyAnimation()) { + if (winAnimator.isAnimating() || winAnimator.mWin.mExiting) { return true; } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 711cf9c..2295656 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3592,7 +3592,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } final Task oldTask = mTaskIdToTask.get(atoken.groupId); - removeAppFromTaskLocked(atoken); + oldTask.removeAppToken(atoken); atoken.groupId = groupId; Task newTask = mTaskIdToTask.get(groupId); @@ -4634,6 +4634,8 @@ public class WindowManagerService extends IWindowManager.Stub } void removeAppFromTaskLocked(AppWindowToken wtoken) { + wtoken.removeAllWindows(); + final Task task = mTaskIdToTask.get(wtoken.groupId); if (task != null) { if (!task.removeAppToken(wtoken)) { diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index ce2ca9b..39b70a8 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -41,6 +41,8 @@ int register_android_server_hdmi_HdmiCecController(JNIEnv* env); int register_android_server_hdmi_HdmiMhlController(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); int register_android_server_PersistentDataBlockService(JNIEnv* env); +int register_android_server_fingerprint_FingerprintService(JNIEnv* env); +int register_android_server_Watchdog(JNIEnv* env); }; using namespace android; @@ -77,6 +79,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) register_android_server_hdmi_HdmiMhlController(env); register_android_server_tv_TvInputHal(env); register_android_server_PersistentDataBlockService(env); + register_android_server_fingerprint_FingerprintService(env); + register_android_server_Watchdog(env); return JNI_VERSION_1_4; } diff --git a/services/devicepolicy/Android.mk b/services/devicepolicy/Android.mk index a55d138..7020f17 100644 --- a/services/devicepolicy/Android.mk +++ b/services/devicepolicy/Android.mk @@ -7,6 +7,6 @@ LOCAL_MODULE := services.devicepolicy LOCAL_SRC_FILES += \ $(call all-java-files-under,java) -LOCAL_JAVA_LIBRARIES := conscrypt +LOCAL_JAVA_LIBRARIES := conscrypt services.core include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index fc96991..d46ae42 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import android.app.admin.DevicePolicyManagerInternal; + import com.android.internal.R; import com.android.internal.os.storage.ExternalStorageFormatter; import com.android.internal.util.FastXmlSerializer; @@ -58,6 +59,7 @@ import android.net.ConnectivityManager; import android.net.Uri; import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; +import android.hardware.usb.UsbManager; import android.net.ProxyInfo; import android.os.Binder; import android.os.Bundle; @@ -3417,8 +3419,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { long ident = Binder.clearCallingIdentity(); try { - mUserManager.setUserRestrictions(new Bundle(), - new UserHandle(UserHandle.USER_OWNER)); + clearUserRestrictions(new UserHandle(UserHandle.USER_OWNER)); if (mDeviceOwner != null) { mDeviceOwner.clearDeviceOwner(); mDeviceOwner.writeOwnerFile(); @@ -3481,7 +3482,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { long ident = Binder.clearCallingIdentity(); try { - mUserManager.setUserRestrictions(new Bundle(), callingUser); + clearUserRestrictions(callingUser); if (mDeviceOwner != null) { mDeviceOwner.removeProfileOwner(callingUser.getIdentifier()); mDeviceOwner.writeOwnerFile(); @@ -3492,6 +3493,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void clearUserRestrictions(UserHandle userHandle) { + AudioManager audioManager = + (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + Bundle userRestrictions = mUserManager.getUserRestrictions(); + mUserManager.setUserRestrictions(new Bundle(), userHandle); + if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) { + audioManager.setMasterMute(false); + } + if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) { + audioManager.setMicrophoneMute(false); + } + } + @Override public boolean hasUserSetupCompleted() { if (!mHasFeature) { @@ -4034,7 +4048,57 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long id = Binder.clearCallingIdentity(); try { + AudioManager audioManager = + (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + boolean alreadyRestricted = mUserManager.hasUserRestriction(key); + + if (enabled && !alreadyRestricted) { + if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, + userHandle.getIdentifier()); + } else if (UserManager.DISALLOW_USB_FILE_TRANSFER.equals(key)) { + UsbManager manager = + (UsbManager) mContext.getSystemService(Context.USB_SERVICE); + manager.setCurrentFunction("none", false); + } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, + userHandle.getIdentifier()); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "", + userHandle.getIdentifier()); + } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) { + Settings.Global.putStringForUser(mContext.getContentResolver(), + Settings.Global.ADB_ENABLED, "0", userHandle.getIdentifier()); + } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) { + Settings.Global.putStringForUser(mContext.getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_ENABLE, "1", + userHandle.getIdentifier()); + Settings.Global.putStringForUser(mContext.getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1", + userHandle.getIdentifier()); + } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.INSTALL_NON_MARKET_APPS, 0, + userHandle.getIdentifier()); + } else if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { + audioManager.setMicrophoneMute(true); + } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { + audioManager.setMasterMute(true); + } + } + mUserManager.setUserRestriction(key, enabled, userHandle); + + if (!enabled && alreadyRestricted) { + if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { + audioManager.setMicrophoneMute(false); + } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { + audioManager.setMasterMute(false); + } + } + } finally { restoreCallingIdentity(id); } diff --git a/services/print/Android.mk b/services/print/Android.mk index 33604b7..00eb2e4 100644 --- a/services/print/Android.mk +++ b/services/print/Android.mk @@ -7,4 +7,6 @@ LOCAL_MODULE := services.print LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_JAVA_LIBRARIES := services.core + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/restrictions/Android.mk b/services/restrictions/Android.mk index fcf8626..57d1c46 100644 --- a/services/restrictions/Android.mk +++ b/services/restrictions/Android.mk @@ -7,4 +7,6 @@ LOCAL_MODULE := services.restrictions LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_JAVA_LIBRARIES := services.core + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/usage/Android.mk b/services/usage/Android.mk index d4b7fa8..f1cbe98 100644 --- a/services/usage/Android.mk +++ b/services/usage/Android.mk @@ -7,4 +7,6 @@ LOCAL_MODULE := services.usage LOCAL_SRC_FILES += \ $(call all-java-files-under,java) +LOCAL_JAVA_LIBRARIES := services.core + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java index d5e4f1b..44aacfc 100644 --- a/telecomm/java/android/telecomm/ConnectionService.java +++ b/telecomm/java/android/telecomm/ConnectionService.java @@ -539,7 +539,10 @@ public abstract class ConnectionService extends Service { connection.getCallerDisplayNamePresentation(), connection.getVideoProvider() == null ? null : connection.getVideoProvider().getInterface(), - connection.getVideoState())); + connection.getVideoState(), + connection.isRequestingRingback(), + connection.getAudioModeIsVoip(), + connection.getStatusHints())); } private void abort(String callId) { diff --git a/telecomm/java/android/telecomm/ParcelableConnection.java b/telecomm/java/android/telecomm/ParcelableConnection.java index 78dd64a..7a87b87 100644 --- a/telecomm/java/android/telecomm/ParcelableConnection.java +++ b/telecomm/java/android/telecomm/ParcelableConnection.java @@ -38,6 +38,9 @@ public final class ParcelableConnection implements Parcelable { private int mCallerDisplayNamePresentation; private IVideoProvider mVideoProvider; private int mVideoState; + private boolean mRequestingRingback; + private boolean mAudioModeIsVoip; + private StatusHints mStatusHints; /** @hide */ public ParcelableConnection( @@ -49,7 +52,10 @@ public final class ParcelableConnection implements Parcelable { String callerDisplayName, int callerDisplayNamePresentation, IVideoProvider videoProvider, - int videoState) { + int videoState, + boolean requestingRingback, + boolean audioModeIsVoip, + StatusHints statusHints) { mPhoneAccount = phoneAccount; mState = state; mCapabilities = capabilities; @@ -59,6 +65,9 @@ public final class ParcelableConnection implements Parcelable { mCallerDisplayNamePresentation = callerDisplayNamePresentation; mVideoProvider = videoProvider; mVideoState = videoState; + mRequestingRingback = requestingRingback; + mAudioModeIsVoip = audioModeIsVoip; + mStatusHints = statusHints; } public PhoneAccountHandle getPhoneAccount() { @@ -98,6 +107,18 @@ public final class ParcelableConnection implements Parcelable { return mVideoState; } + public boolean isRequestingRingback() { + return mRequestingRingback; + } + + public boolean getAudioModeIsVoip() { + return mAudioModeIsVoip; + } + + public final StatusHints getStatusHints() { + return mStatusHints; + } + @Override public String toString() { return new StringBuilder() @@ -126,6 +147,9 @@ public final class ParcelableConnection implements Parcelable { IVideoProvider videoCallProvider = IVideoProvider.Stub.asInterface(source.readStrongBinder()); int videoState = source.readInt(); + boolean requestingRingback = source.readByte() == 1; + boolean audioModeIsVoip = source.readByte() == 1; + StatusHints statusHints = source.readParcelable(classLoader); return new ParcelableConnection( phoneAccount, @@ -136,7 +160,10 @@ public final class ParcelableConnection implements Parcelable { callerDisplayName, callerDisplayNamePresentation, videoCallProvider, - videoState); + videoState, + requestingRingback, + audioModeIsVoip, + statusHints); } @Override @@ -164,5 +191,8 @@ public final class ParcelableConnection implements Parcelable { destination.writeStrongBinder( mVideoProvider != null ? mVideoProvider.asBinder() : null); destination.writeInt(mVideoState); + destination.writeByte((byte) (mRequestingRingback ? 1 : 0)); + destination.writeByte((byte) (mAudioModeIsVoip ? 1 : 0)); + destination.writeParcelable(mStatusHints, 0); } } diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java index 13b0834..d3972d3 100644 --- a/telecomm/java/android/telecomm/RemoteConnection.java +++ b/telecomm/java/android/telecomm/RemoteConnection.java @@ -24,9 +24,11 @@ import android.os.RemoteException; import android.telephony.DisconnectCause; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * A connection provided to a {@link ConnectionService} by another {@code ConnectionService} @@ -182,7 +184,8 @@ public final class RemoteConnection { private IConnectionService mConnectionService; private final String mConnectionId; - private final Set<Listener> mListeners = new HashSet<>(); + private final Set<Listener> mListeners = Collections.newSetFromMap( + new ConcurrentHashMap<Listener, Boolean>(2)); private final Set<RemoteConnection> mConferenceableConnections = new HashSet<>(); private int mState = Connection.STATE_NEW; diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml index 9d78ca5..95072a4 100644 --- a/tests/OneMedia/AndroidManifest.xml +++ b/tests/OneMedia/AndroidManifest.xml @@ -23,7 +23,7 @@ </activity> <service android:name="com.android.onemedia.OnePlayerService" - android:exported="false" + android:exported="true" android:process="com.android.onemedia.service" /> <service android:name=".provider.OneMediaRouteProvider" diff --git a/tests/OneMedia/res/drawable/ic_fast_forward.xml b/tests/OneMedia/res/drawable/ic_fast_forward.xml new file mode 100644 index 0000000..8daf07d --- /dev/null +++ b/tests/OneMedia/res/drawable/ic_fast_forward.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="36dp" + android:viewportWidth="24" + android:viewportHeight="24" > + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M4.0,18.0l8.5,-6.0L4.0,6.0L4.0,18.0zM13.0,6.0l0.0,12.0l8.5,-6.0L13.0,6.0z"/> +</vector> diff --git a/tests/OneMedia/res/drawable/ic_fast_rewind.xml b/tests/OneMedia/res/drawable/ic_fast_rewind.xml new file mode 100644 index 0000000..4ed1f54 --- /dev/null +++ b/tests/OneMedia/res/drawable/ic_fast_rewind.xml @@ -0,0 +1,24 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="36dp" + android:viewportWidth="24" + android:viewportHeight="24" > + <path + android:fillColor="#FFFFFFFF" + android:pathData="M11.0,18.0L11.0,6.0l-8.5,6.0L11.0,18.0zM11.5,12.0l8.5,6.0L20.0,6.0L11.5,12.0z"/> +</vector> diff --git a/tests/OneMedia/res/drawable/ic_pause.xml b/tests/OneMedia/res/drawable/ic_pause.xml new file mode 100644 index 0000000..15d0756 --- /dev/null +++ b/tests/OneMedia/res/drawable/ic_pause.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="36dp" + android:viewportWidth="24" + android:viewportHeight="24" > + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M6.0,19.0l4.0,0.0L10.0,5.0L6.0,5.0L6.0,19.0zM14.0,5.0l0.0,14.0l4.0,0.0L18.0,5.0L14.0,5.0z"/> +</vector> diff --git a/tests/OneMedia/res/drawable/ic_play_arrow.xml b/tests/OneMedia/res/drawable/ic_play_arrow.xml new file mode 100644 index 0000000..49d766d --- /dev/null +++ b/tests/OneMedia/res/drawable/ic_play_arrow.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="36dp" + android:viewportWidth="24" + android:viewportHeight="24" > + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M8.0,5.0l0.0,14.0 11.0,-7.0z"/> +</vector> diff --git a/tests/OneMedia/res/drawable/ic_skip_next.xml b/tests/OneMedia/res/drawable/ic_skip_next.xml new file mode 100644 index 0000000..8a6ceca --- /dev/null +++ b/tests/OneMedia/res/drawable/ic_skip_next.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="36dp" + android:viewportWidth="24" + android:viewportHeight="24" > + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M6.0,18.0l8.5,-6.0L6.0,6.0L6.0,18.0zM16.0,6.0l0.0,12.0l2.0,0.0L18.0,6.0L16.0,6.0z"/> +</vector> diff --git a/tests/OneMedia/res/drawable/ic_skip_previous.xml b/tests/OneMedia/res/drawable/ic_skip_previous.xml new file mode 100644 index 0000000..c5d07db --- /dev/null +++ b/tests/OneMedia/res/drawable/ic_skip_previous.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="36dp" + android:viewportWidth="24" + android:viewportHeight="24" > + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M6.0,6.0l2.0,0.0l0.0,12.0l-2.0,0.0z"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M9.5,12.0l8.5,6.0 0.0,-12.0z"/> +</vector> diff --git a/tests/OneMedia/res/drawable/ic_stop.xml b/tests/OneMedia/res/drawable/ic_stop.xml new file mode 100644 index 0000000..6043fdb --- /dev/null +++ b/tests/OneMedia/res/drawable/ic_stop.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="36dp" + android:viewportWidth="24" + android:viewportHeight="24" > + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M6.0,6.0l12.0,0.0l0.0,12.0l-12.0,0.0z"/> +</vector> diff --git a/tests/OneMedia/res/layout/activity_one_player.xml b/tests/OneMedia/res/layout/activity_one_player.xml index 516562f..ce2d641 100644 --- a/tests/OneMedia/res/layout/activity_one_player.xml +++ b/tests/OneMedia/res/layout/activity_one_player.xml @@ -33,6 +33,19 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/has_video" /> + <ImageView + android:id="@+id/art" + android:layout_width="match_parent" + android:layout_height="96dp" + android:scaleType="centerCrop" + android:visibility="gone" + /> + <Button + android:id="@+id/art_picker" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/art_picker" + /> <LinearLayout android:id="@+id/controls" android:layout_width="match_parent" diff --git a/tests/OneMedia/res/values/strings.xml b/tests/OneMedia/res/values/strings.xml index 3735c8d..86657fd 100644 --- a/tests/OneMedia/res/values/strings.xml +++ b/tests/OneMedia/res/values/strings.xml @@ -12,5 +12,5 @@ <string name="media_next_hint">Next content</string> <string name="has_video">Is video</string> <string name="has_duration">Has duration</string> - + <string name="art_picker">Choose artwork</string> </resources> diff --git a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl index d4df4c5..f53eac0 100644 --- a/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl +++ b/tests/OneMedia/src/com/android/onemedia/IPlayerService.aidl @@ -15,6 +15,7 @@ package com.android.onemedia; +import android.graphics.Bitmap; import android.media.session.MediaSession; import android.os.Bundle; @@ -26,4 +27,5 @@ interface IPlayerService { void registerCallback(in IPlayerCallback cb); void unregisterCallback(in IPlayerCallback cb); void sendRequest(String action, in Bundle params, in IRequestCallback cb); + void setIcon(in Bitmap icon); } diff --git a/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java b/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java new file mode 100644 index 0000000..a5bcda5 --- /dev/null +++ b/tests/OneMedia/src/com/android/onemedia/NotificationHelper.java @@ -0,0 +1,234 @@ +package com.android.onemedia; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Bitmap; +import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.text.format.DateUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.android.onemedia.playback.RequestUtils; + +/** + * Keeps track of a notification and updates it automatically for a given + * MediaSession. + */ +public class NotificationHelper extends BroadcastReceiver { + private static final String TAG = "NotificationHelper"; + + private static final int NOTIFICATION_ID = 433; // John Cage, 1952 + + private final Service mService; + private final MediaSession mSession; + private final MediaController mController; + private final MediaController.TransportControls mTransportControls; + private final SparseArray<PendingIntent> mIntents = new SparseArray<PendingIntent>(); + + private PlaybackState mPlaybackState; + private MediaMetadata mMetadata; + + private boolean mStarted = false; + + public NotificationHelper(Service service, MediaSession session) { + mService = service; + mSession = session; + mController = session.getController(); + mTransportControls = mController.getTransportControls(); + String pkg = mService.getPackageName(); + + mIntents.put(R.drawable.ic_pause, PendingIntent.getBroadcast(mService, 100, new Intent( + com.android.onemedia.playback.RequestUtils.ACTION_PAUSE).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_play_arrow, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_PLAY).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_skip_previous, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_PREV).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_skip_next, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_NEXT).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_fast_rewind, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_REW).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + mIntents.put(R.drawable.ic_fast_forward, PendingIntent.getBroadcast(mService, 100, + new Intent(com.android.onemedia.playback.RequestUtils.ACTION_FFWD).setPackage(pkg), + PendingIntent.FLAG_CANCEL_CURRENT)); + } + + /** + * Posts the notification and starts tracking the session to keep it + * updated. The notification will automatically be removed if the session is + * destroyed before {@link #onStop} is called. + */ + public void onStart() { + mController.addCallback(mCb); + IntentFilter filter = new IntentFilter(); + filter.addAction(RequestUtils.ACTION_FFWD); + filter.addAction(RequestUtils.ACTION_NEXT); + filter.addAction(RequestUtils.ACTION_PAUSE); + filter.addAction(RequestUtils.ACTION_PLAY); + filter.addAction(RequestUtils.ACTION_PREV); + filter.addAction(RequestUtils.ACTION_REW); + mService.registerReceiver(this, filter); + + mMetadata = mController.getMetadata(); + mPlaybackState = mController.getPlaybackState(); + + mStarted = true; + // The notification must be updated after setting started to true + updateNotification(); + } + + /** + * Removes the notification and stops tracking the session. If the session + * was destroyed this has no effect. + */ + public void onStop() { + mStarted = false; + mController.removeCallback(mCb); + mService.unregisterReceiver(this); + updateNotification(); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + Log.d(TAG, "Received intent with action " + action); + if (RequestUtils.ACTION_PAUSE.equals(action)) { + mTransportControls.pause(); + } else if (RequestUtils.ACTION_PLAY.equals(action)) { + mTransportControls.play(); + } else if (RequestUtils.ACTION_NEXT.equals(action)) { + mTransportControls.skipToNext(); + } else if (RequestUtils.ACTION_PREV.equals(action)) { + mTransportControls.skipToPrevious(); + } else if (RequestUtils.ACTION_REW.equals(action)) { + mTransportControls.rewind(); + } else if (RequestUtils.ACTION_FFWD.equals(action)) { + mTransportControls.fastForward(); + } + + } + + private final MediaController.Callback mCb = new MediaController.Callback() { + @Override + public void onPlaybackStateChanged(PlaybackState state) { + mPlaybackState = state; + Log.d(TAG, "Received new playback state" + state); + updateNotification(); + } + + @Override + public void onMetadataChanged(MediaMetadata metadata) { + mMetadata = metadata; + Log.d(TAG, "Received new metadata " + metadata); + updateNotification(); + } + }; + + NotificationManager mNoMan = null; + + private void updateNotification() { + if (mNoMan == null) { + mNoMan = (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE); + } + if (mPlaybackState == null) { + mNoMan.cancel(NOTIFICATION_ID); + return; + } + if (!mStarted) { + mNoMan.cancel(NOTIFICATION_ID); + return; + } + + String status; + final int state = mPlaybackState.getState(); + switch (state) { + case PlaybackState.STATE_PLAYING: + status = "PLAYING: "; + break; + case PlaybackState.STATE_PAUSED: + status = "PAUSED: "; + break; + case PlaybackState.STATE_STOPPED: + status = "STOPPED: "; + break; + case PlaybackState.STATE_ERROR: + status = "ERROR: "; + break; + case PlaybackState.STATE_BUFFERING: + status = "BUFFERING: "; + break; + case PlaybackState.STATE_NONE: + default: + status = ""; + break; + } + CharSequence title, text; + Bitmap art; + if (mMetadata == null) { + title = status; + text = "Empty metadata!"; + art = null; + } else { + MediaMetadata.Description description = mMetadata.getDescription(); + title = description.getTitle(); + text = description.getSubtitle(); + art = description.getIcon(); + } + + String playPauseLabel = ""; + int playPauseIcon; + if (state == PlaybackState.STATE_PLAYING) { + playPauseLabel = "Pause"; + playPauseIcon = R.drawable.ic_pause; + } else { + playPauseLabel = "Play"; + playPauseIcon = R.drawable.ic_play_arrow; + } + + final long pos = mPlaybackState.getPosition(); + final long end = mMetadata == null ? 0 : mMetadata + .getLong(MediaMetadata.METADATA_KEY_DURATION); + Notification notification = new Notification.Builder(mService) + .setSmallIcon(android.R.drawable.stat_notify_chat) + .setContentTitle(title) + .setContentText(text) + .setShowWhen(false) + .setContentInfo(DateUtils.formatElapsedTime(pos)) + .setProgress((int) end, (int) pos, false) + .setLargeIcon(art) + .addAction(R.drawable.ic_skip_previous, "Previous", + mIntents.get(R.drawable.ic_skip_previous)) + .addAction(R.drawable.ic_fast_rewind, "Rewind", + mIntents.get(R.drawable.ic_fast_rewind)) + .addAction(playPauseIcon, playPauseLabel, + mIntents.get(playPauseIcon)) + .addAction(R.drawable.ic_fast_forward, "Fast Forward", + mIntents.get(R.drawable.ic_fast_forward)) + .addAction(R.drawable.ic_skip_next, "Next", + mIntents.get(R.drawable.ic_skip_next)) + .setStyle(new Notification.MediaStyle() + .setShowActionsInCompactView(2) + .setMediaSession(mSession.getSessionToken())) + .setColor(0xFFDB4437) + .build(); + + mService.startForeground(NOTIFICATION_ID, notification); + } + +} diff --git a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java index 894377b..2ff3e20 100644 --- a/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java +++ b/tests/OneMedia/src/com/android/onemedia/OnePlayerActivity.java @@ -17,20 +17,35 @@ package com.android.onemedia; import android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.media.MediaMetadata; import android.media.session.PlaybackState; +import android.net.Uri; import android.os.Bundle; +import android.provider.MediaStore; +import android.text.format.DateUtils; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; +import android.widget.ImageView; import android.widget.TextView; +import java.io.IOException; + public class OnePlayerActivity extends Activity { private static final String TAG = "OnePlayerActivity"; + private static final int READ_REQUEST_CODE = 42; + protected PlayerController mPlayer; private Button mStartButton; @@ -41,8 +56,10 @@ public class OnePlayerActivity extends Activity { private EditText mContentText; private EditText mNextContentText; private CheckBox mHasVideo; + private ImageView mArtView; - private int mPlaybackState; + private PlaybackState mPlaybackState; + private Bitmap mAlbumArtBitmap; @Override protected void onCreate(Bundle savedInstanceState) { @@ -58,6 +75,10 @@ public class OnePlayerActivity extends Activity { mContentText = (EditText) findViewById(R.id.content); mNextContentText = (EditText) findViewById(R.id.next_content); mHasVideo = (CheckBox) findViewById(R.id.has_video); + mArtView = (ImageView) findViewById(R.id.art); + + final Button artPicker = (Button) findViewById(R.id.art_picker); + artPicker.setOnClickListener(mButtonListener); mStartButton.setOnClickListener(mButtonListener); mPlayButton.setOnClickListener(mButtonListener); @@ -86,6 +107,31 @@ public class OnePlayerActivity extends Activity { super.onPause(); } + @Override + public void onActivityResult(int requestCode, int resultCode, + Intent resultData) { + if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + Uri uri = null; + if (resultData != null) { + uri = resultData.getData(); + Log.i(TAG, "Uri: " + uri.toString()); + mAlbumArtBitmap = null; + try { + mAlbumArtBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri); + } catch (IOException e) { + Log.v(TAG, "Couldn't load album art", e); + } + mArtView.setImageBitmap(mAlbumArtBitmap); + if (mAlbumArtBitmap != null) { + mArtView.setVisibility(View.VISIBLE); + } else { + mArtView.setVisibility(View.GONE); + } + mPlayer.setArt(mAlbumArtBitmap); + } + } + } + private void setControlsEnabled(boolean enabled) { mStartButton.setEnabled(enabled); mPlayButton.setEnabled(enabled); @@ -94,36 +140,46 @@ public class OnePlayerActivity extends Activity { private View.OnClickListener mButtonListener = new View.OnClickListener() { @Override public void onClick(View v) { + final int state = mPlaybackState.getState(); switch (v.getId()) { case R.id.play_button: - Log.d(TAG, "Play button pressed, in state " + mPlaybackState); - if (mPlaybackState == PlaybackState.STATE_PAUSED - || mPlaybackState == PlaybackState.STATE_STOPPED) { + Log.d(TAG, "Play button pressed, in state " + state); + if (state == PlaybackState.STATE_PAUSED + || state == PlaybackState.STATE_STOPPED) { mPlayer.play(); - } else if (mPlaybackState == PlaybackState.STATE_PLAYING) { + } else if (state == PlaybackState.STATE_PLAYING) { mPlayer.pause(); } break; case R.id.start_button: - Log.d(TAG, "Start button pressed, in state " + mPlaybackState); + Log.d(TAG, "Start button pressed, in state " + state); mPlayer.setContent(mContentText.getText().toString()); break; case R.id.route_button: mPlayer.showRoutePicker(); break; + case R.id.art_picker: + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("image/*"); + + startActivityForResult(intent, READ_REQUEST_CODE); + break; } } }; private PlayerController.Listener mListener = new PlayerController.Listener() { + public MediaMetadata mMetadata; + @Override public void onPlaybackStateChange(PlaybackState state) { - mPlaybackState = state.getState(); + mPlaybackState = state; boolean enablePlay = false; boolean enableControls = true; StringBuilder statusBuilder = new StringBuilder(); - switch (mPlaybackState) { + switch (mPlaybackState.getState()) { case PlaybackState.STATE_PLAYING: statusBuilder.append("playing"); mPlayButton.setText("Pause"); @@ -172,7 +228,7 @@ public class OnePlayerActivity extends Activity { @Override public void onMetadataChange(MediaMetadata metadata) { - Log.d(TAG, "Metadata update! Title: " + metadata); + mMetadata = metadata; } }; } diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java index c0799fc..c8d72ca 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java @@ -30,6 +30,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.graphics.Bitmap; import android.util.Log; import com.android.onemedia.playback.RequestUtils; @@ -52,6 +53,7 @@ public class PlayerController { private Handler mHandler = new Handler(); private boolean mResumed; + private Bitmap mArt; public PlayerController(Activity context, Intent serviceIntent) { mContext = context; @@ -89,6 +91,16 @@ public class PlayerController { unbindFromService(); } + public void setArt(Bitmap art) { + mArt = art; + if (mBinder != null) { + try { + mBinder.setIcon(art); + } catch (RemoteException e) { + } + } + } + public void play() { if (mTransportControls != null) { mTransportControls.play(); @@ -125,6 +137,16 @@ public class PlayerController { // TODO } + public MediaSession.Token getSessionToken() { + if (mBinder != null) { + try { + return mBinder.getSessionToken(); + } catch (RemoteException e) { + } + } + return null; + } + private void unbindFromService() { mContext.unbindService(mServiceConnection); } @@ -165,6 +187,9 @@ public class PlayerController { mContext.setMediaController(mController); mController.addCallback(mControllerCb, mHandler); mTransportControls = mController.getTransportControls(); + if (mArt != null) { + setArt(mArt); + } Log.d(TAG, "Ready to use PlayerService"); if (mListener != null) { @@ -194,6 +219,9 @@ public class PlayerController { return; } Log.d(TAG, "Received metadata change, " + metadata.getDescription()); + if (mListener != null) { + mListener.onMetadataChange(metadata); + } } } diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerService.java b/tests/OneMedia/src/com/android/onemedia/PlayerService.java index 58ee4a1..9967c99 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerService.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerService.java @@ -17,6 +17,7 @@ package com.android.onemedia; import android.app.Service; import android.content.Intent; +import android.graphics.Bitmap; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.os.Bundle; @@ -34,6 +35,7 @@ public class PlayerService extends Service { private PlayerBinder mBinder; private PlayerSession mSession; + private NotificationHelper mNotifyHelper; private Intent mIntent; private boolean mStarted = false; @@ -47,6 +49,7 @@ public class PlayerService extends Service { mSession = onCreatePlayerController(); mSession.createSession(); mSession.setListener(mPlayerListener); + mNotifyHelper = new NotificationHelper(this, mSession.mSession); } } @@ -75,6 +78,7 @@ public class PlayerService extends Service { if (!mStarted) { Log.d(TAG, "Starting self"); startService(onCreateServiceIntent()); + mNotifyHelper.onStart(); mStarted = true; } } @@ -82,6 +86,7 @@ public class PlayerService extends Service { public void onPlaybackEnded() { if (mStarted) { Log.d(TAG, "Stopping self"); + mNotifyHelper.onStop(); stopSelf(); mStarted = false; } @@ -150,8 +155,17 @@ public class PlayerService extends Service { @Override public MediaSession.Token getSessionToken() throws RemoteException { + if (mSession == null) { + Log.e(TAG, "Error in PlayerService: mSession=null in getSessionToken()"); + return null; + } return mSession.getSessionToken(); } + + @Override + public void setIcon(Bitmap icon) { + mSession.setIcon(icon); + } } } diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 890d68d..9afcf24 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -17,6 +17,8 @@ package com.android.onemedia; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.media.MediaMetadata; import android.media.routing.MediaRouteSelector; import android.media.routing.MediaRouter; import android.media.routing.MediaRouter.ConnectionRequest; @@ -50,6 +52,7 @@ public class PlayerSession { protected Renderer mRenderer; protected MediaSession.Callback mCallback; protected Renderer.Listener mRenderListener; + protected MediaMetadata.Builder mMetadataBuilder; protected PlaybackState mPlaybackState; protected Listener mListener; @@ -66,6 +69,8 @@ public class PlayerSession { mPlaybackState = psBob.build(); mRenderer.registerListener(mRenderListener); + + initMetadata(); } public void createSession() { @@ -92,6 +97,7 @@ public class PlayerSession { | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); mSession.setMediaRouter(mRouter); mSession.setActive(true); + updateMetadata(); } public void onDestroy() { @@ -130,6 +136,19 @@ public class PlayerSession { mRenderer.setNextContent(request); } + public void setIcon(Bitmap icon) { + mMetadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, icon); + updateMetadata(); + } + + private void updateMetadata() { + // This is a mild abuse of metadata and shouldn't be duplicated in real + // code + if (mSession != null && mSession.isActive()) { + mSession.setMetadata(mMetadataBuilder.build()); + } + } + private void updateState(int newState) { float rate = newState == PlaybackState.STATE_PLAYING ? 1 : 0; long position = mRenderer == null ? -1 : mRenderer.getSeekPosition(); @@ -140,6 +159,14 @@ public class PlayerSession { mSession.setPlaybackState(mPlaybackState); } + private void initMetadata() { + mMetadataBuilder = new MediaMetadata.Builder(); + mMetadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, + "OneMedia display title"); + mMetadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, + "OneMedia display subtitle"); + } + public interface Listener { public void onPlayStateChanged(PlaybackState state); } diff --git a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java index 3778c5f..1688395 100644 --- a/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java +++ b/tests/OneMedia/src/com/android/onemedia/playback/RequestUtils.java @@ -26,6 +26,12 @@ import java.util.Map; public class RequestUtils { public static final String ACTION_SET_CONTENT = "set_content"; public static final String ACTION_SET_NEXT_CONTENT = "set_next_content"; + public static final String ACTION_PAUSE = "com.android.onemedia.pause"; + public static final String ACTION_PLAY = "com.android.onemedia.play"; + public static final String ACTION_REW = "com.android.onemedia.rew"; + public static final String ACTION_FFWD = "com.android.onemedia.ffwd"; + public static final String ACTION_PREV = "com.android.onemedia.prev"; + public static final String ACTION_NEXT = "com.android.onemedia.next"; public static final String EXTRA_KEY_SOURCE = "source"; public static final String EXTRA_KEY_METADATA = "metadata"; diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index cf3dd0a..137c85c 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -485,9 +485,13 @@ static void get_outline(image_info* image) find_max_opacity(image->rows, innerStartX, innerStartY, innerMidX, innerMidY, 1, 1, &diagonalInset); - // Determine source radius based upon inset - // radius = 1 / (sqrt(2) - 1) * inset - image->outlineRadius = 2.4142f * diagonalInset; + /* Determine source radius based upon inset: + * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r + * sqrt(2) * r = sqrt(2) * i + r + * (sqrt(2) - 1) * r = sqrt(2) * i + * r = sqrt(2) / (sqrt(2) - 1) * i + */ + image->outlineRadius = 3.4142f * diagonalInset; NOISY(printf("outline insets %d %d %d %d, rad %f, alpha %x\n", image->outlineInsetsLeft, |
