diff options
111 files changed, 3858 insertions, 1421 deletions
diff --git a/api/current.txt b/api/current.txt index dd2549d..74a97b6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1316,6 +1316,8 @@ package android { field public static final int topRightRadius = 16843178; // 0x10101aa field public static final int touchscreenBlocksFocus = 16843919; // 0x101048f field public static final int track = 16843631; // 0x101036f + field public static final int trackTint = 16843993; // 0x10104d9 + field public static final int trackTintMode = 16843994; // 0x10104da field public static final int transcriptMode = 16843008; // 0x1010100 field public static final int transformPivotX = 16843552; // 0x1010320 field public static final int transformPivotY = 16843553; // 0x1010321 @@ -14166,6 +14168,7 @@ package android.media { method public boolean isMicrophoneMute(); method public boolean isMusicActive(); method public boolean isSpeakerphoneOn(); + method public boolean isStreamMute(int); method public boolean isVolumeFixed(); method public deprecated boolean isWiredHeadsetOn(); method public void loadSoundEffects(); @@ -14184,8 +14187,8 @@ package android.media { method public void setRingerMode(int); method public deprecated void setRouting(int, int, int); method public void setSpeakerphoneOn(boolean); - method public void setStreamMute(int, boolean); - method public void setStreamSolo(int, boolean); + method public deprecated void setStreamMute(int, boolean); + method public deprecated void setStreamSolo(int, boolean); method public void setStreamVolume(int, int, int); method public deprecated void setVibrateSetting(int, int); method public deprecated void setWiredHeadsetOn(boolean); @@ -14203,8 +14206,11 @@ package android.media { field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED"; field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED"; field public static final int ADJUST_LOWER = -1; // 0xffffffff + field public static final int ADJUST_MUTE = -100; // 0xffffff9c field public static final int ADJUST_RAISE = 1; // 0x1 field public static final int ADJUST_SAME = 0; // 0x0 + field public static final int ADJUST_TOGGLE_MUTE = 101; // 0x65 + field public static final int ADJUST_UNMUTE = 100; // 0x64 field public static final int AUDIOFOCUS_GAIN = 1; // 0x1 field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2 field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4 @@ -38453,6 +38459,8 @@ package android.widget { method public void setClippingEnabled(boolean); method public void setContentView(android.view.View); method public void setElevation(float); + method public void setEnterTransition(android.transition.Transition); + method public void setExitTransition(android.transition.Transition); method public void setFocusable(boolean); method public void setHeight(int); method public void setIgnoreCheekPress(); @@ -39054,7 +39062,11 @@ package android.widget { method public java.lang.CharSequence getTextOn(); method public android.graphics.drawable.Drawable getThumbDrawable(); method public int getThumbTextPadding(); + method public android.content.res.ColorStateList getThumbTintList(); + method public android.graphics.PorterDuff.Mode getThumbTintMode(); method public android.graphics.drawable.Drawable getTrackDrawable(); + method public android.content.res.ColorStateList getTrackTintList(); + method public android.graphics.PorterDuff.Mode getTrackTintMode(); method public void onMeasure(int, int); method public void setShowText(boolean); method public void setSplitTrack(boolean); @@ -39068,8 +39080,12 @@ package android.widget { method public void setThumbDrawable(android.graphics.drawable.Drawable); method public void setThumbResource(int); method public void setThumbTextPadding(int); + method public void setThumbTintList(android.content.res.ColorStateList); + method public void setThumbTintMode(android.graphics.PorterDuff.Mode); method public void setTrackDrawable(android.graphics.drawable.Drawable); method public void setTrackResource(int); + method public void setTrackTintList(android.content.res.ColorStateList); + method public void setTrackTintMode(android.graphics.PorterDuff.Mode); } public class TabHost extends android.widget.FrameLayout implements android.view.ViewTreeObserver.OnTouchModeChangeListener { diff --git a/api/system-current.txt b/api/system-current.txt index 8d503d6..27b7a91 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1392,6 +1392,8 @@ package android { field public static final int topRightRadius = 16843178; // 0x10101aa field public static final int touchscreenBlocksFocus = 16843919; // 0x101048f field public static final int track = 16843631; // 0x101036f + field public static final int trackTint = 16843993; // 0x10104d9 + field public static final int trackTintMode = 16843994; // 0x10104da field public static final int transcriptMode = 16843008; // 0x1010100 field public static final int transformPivotX = 16843552; // 0x1010320 field public static final int transformPivotY = 16843553; // 0x1010321 @@ -15144,6 +15146,7 @@ package android.media { method public boolean isMicrophoneMute(); method public boolean isMusicActive(); method public boolean isSpeakerphoneOn(); + method public boolean isStreamMute(int); method public boolean isVolumeFixed(); method public deprecated boolean isWiredHeadsetOn(); method public void loadSoundEffects(); @@ -15165,8 +15168,8 @@ package android.media { method public void setRingerMode(int); method public deprecated void setRouting(int, int, int); method public void setSpeakerphoneOn(boolean); - method public void setStreamMute(int, boolean); - method public void setStreamSolo(int, boolean); + method public deprecated void setStreamMute(int, boolean); + method public deprecated void setStreamSolo(int, boolean); method public void setStreamVolume(int, int, int); method public deprecated void setVibrateSetting(int, int); method public deprecated void setWiredHeadsetOn(boolean); @@ -15185,8 +15188,11 @@ package android.media { field public static final deprecated java.lang.String ACTION_SCO_AUDIO_STATE_CHANGED = "android.media.SCO_AUDIO_STATE_CHANGED"; field public static final java.lang.String ACTION_SCO_AUDIO_STATE_UPDATED = "android.media.ACTION_SCO_AUDIO_STATE_UPDATED"; field public static final int ADJUST_LOWER = -1; // 0xffffffff + field public static final int ADJUST_MUTE = -100; // 0xffffff9c field public static final int ADJUST_RAISE = 1; // 0x1 field public static final int ADJUST_SAME = 0; // 0x0 + field public static final int ADJUST_TOGGLE_MUTE = 101; // 0x65 + field public static final int ADJUST_UNMUTE = 100; // 0x64 field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1 field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4 field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2 @@ -40948,6 +40954,8 @@ package android.widget { method public void setClippingEnabled(boolean); method public void setContentView(android.view.View); method public void setElevation(float); + method public void setEnterTransition(android.transition.Transition); + method public void setExitTransition(android.transition.Transition); method public void setFocusable(boolean); method public void setHeight(int); method public void setIgnoreCheekPress(); @@ -41549,7 +41557,11 @@ package android.widget { method public java.lang.CharSequence getTextOn(); method public android.graphics.drawable.Drawable getThumbDrawable(); method public int getThumbTextPadding(); + method public android.content.res.ColorStateList getThumbTintList(); + method public android.graphics.PorterDuff.Mode getThumbTintMode(); method public android.graphics.drawable.Drawable getTrackDrawable(); + method public android.content.res.ColorStateList getTrackTintList(); + method public android.graphics.PorterDuff.Mode getTrackTintMode(); method public void onMeasure(int, int); method public void setShowText(boolean); method public void setSplitTrack(boolean); @@ -41563,8 +41575,12 @@ package android.widget { method public void setThumbDrawable(android.graphics.drawable.Drawable); method public void setThumbResource(int); method public void setThumbTextPadding(int); + method public void setThumbTintList(android.content.res.ColorStateList); + method public void setThumbTintMode(android.graphics.PorterDuff.Mode); method public void setTrackDrawable(android.graphics.drawable.Drawable); method public void setTrackResource(int); + method public void setTrackTintList(android.content.res.ColorStateList); + method public void setTrackTintMode(android.graphics.PorterDuff.Mode); } public class TabHost extends android.widget.FrameLayout implements android.view.ViewTreeObserver.OnTouchModeChangeListener { diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 2a0ed90..fb3d423 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -1735,6 +1735,22 @@ public class Am extends BaseCommand { int right = Integer.valueOf(rightStr); String bottomStr = nextArgRequired(); int bottom = Integer.valueOf(bottomStr); + if (left < 0) { + System.err.println("Error: bad left arg: " + leftStr); + return; + } + if (top < 0) { + System.err.println("Error: bad top arg: " + topStr); + return; + } + if (right <= 0) { + System.err.println("Error: bad right arg: " + rightStr); + return; + } + if (bottom <= 0) { + System.err.println("Error: bad bottom arg: " + bottomStr); + return; + } try { mAm.resizeStack(stackId, new Rect(left, top, right, bottom)); diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 379fe11..97b9f4c 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -766,6 +766,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_FOCUSED_STACK_ID_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int focusedStackId = getFocusedStackId(); + reply.writeNoException(); + reply.writeInt(focusedStackId); + return true; + } + case REGISTER_TASK_STACK_LISTENER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -3290,6 +3298,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } @Override + public int getFocusedStackId() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_FOCUSED_STACK_ID_TRANSACTION, data, reply, 0); + reply.readException(); + int focusedStackId = reply.readInt(); + data.recycle(); + reply.recycle(); + return focusedStackId; + } + @Override public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl index 52884f7..ff1175f 100644 --- a/core/java/android/app/IActivityContainer.aidl +++ b/core/java/android/app/IActivityContainer.aidl @@ -32,6 +32,7 @@ interface IActivityContainer { void checkEmbeddedAllowed(in Intent intent); void checkEmbeddedAllowedIntentSender(in IIntentSender intentSender); int getDisplayId(); + int getStackId(); boolean injectEvent(in InputEvent event); void release(); } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index f152c6f..efc4543 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -139,6 +139,7 @@ public interface IActivityManager extends IInterface { public StackInfo getStackInfo(int stackId) throws RemoteException; public boolean isInHomeStack(int taskId) throws RemoteException; public void setFocusedStack(int stackId) throws RemoteException; + public int getFocusedStackId() throws RemoteException; public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException; public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; public ContentProviderHolder getContentProvider(IApplicationThread caller, @@ -800,4 +801,5 @@ public interface IActivityManager extends IInterface { // Start of M transactions int NOTIFY_CLEARTEXT_NETWORK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+280; int CREATE_STACK_ON_DISPLAY = IBinder.FIRST_CALL_TRANSACTION+281; + int GET_FOCUSED_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+282; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 55c3960..bedec72 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -694,7 +694,7 @@ public class DevicePolicyManager { public void setPasswordQuality(ComponentName admin, int quality) { if (mService != null) { try { - mService.setPasswordQuality(admin, quality, UserHandle.myUserId()); + mService.setPasswordQuality(admin, quality); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -747,7 +747,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLength(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLength(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumLength(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -801,7 +801,7 @@ public class DevicePolicyManager { public void setPasswordMinimumUpperCase(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumUpperCase(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumUpperCase(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -862,7 +862,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLowerCase(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLowerCase(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumLowerCase(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -922,7 +922,7 @@ public class DevicePolicyManager { public void setPasswordMinimumLetters(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumLetters(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumLetters(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -980,7 +980,7 @@ public class DevicePolicyManager { public void setPasswordMinimumNumeric(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumNumeric(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumNumeric(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1039,7 +1039,7 @@ public class DevicePolicyManager { public void setPasswordMinimumSymbols(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumSymbols(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumSymbols(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1097,7 +1097,7 @@ public class DevicePolicyManager { public void setPasswordMinimumNonLetter(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordMinimumNonLetter(admin, length, UserHandle.myUserId()); + mService.setPasswordMinimumNonLetter(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1157,7 +1157,7 @@ public class DevicePolicyManager { public void setPasswordHistoryLength(ComponentName admin, int length) { if (mService != null) { try { - mService.setPasswordHistoryLength(admin, length, UserHandle.myUserId()); + mService.setPasswordHistoryLength(admin, length); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1189,7 +1189,7 @@ public class DevicePolicyManager { public void setPasswordExpirationTimeout(ComponentName admin, long timeout) { if (mService != null) { try { - mService.setPasswordExpirationTimeout(admin, timeout, UserHandle.myUserId()); + mService.setPasswordExpirationTimeout(admin, timeout); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1334,7 +1334,7 @@ public class DevicePolicyManager { public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) { if (mService != null) { try { - mService.setMaximumFailedPasswordsForWipe(admin, num, UserHandle.myUserId()); + mService.setMaximumFailedPasswordsForWipe(admin, num); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1418,7 +1418,7 @@ public class DevicePolicyManager { public boolean resetPassword(String password, int flags) { if (mService != null) { try { - return mService.resetPassword(password, flags, UserHandle.myUserId()); + return mService.resetPassword(password, flags); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1442,7 +1442,7 @@ public class DevicePolicyManager { public void setMaximumTimeToLock(ComponentName admin, long timeMs) { if (mService != null) { try { - mService.setMaximumTimeToLock(admin, timeMs, UserHandle.myUserId()); + mService.setMaximumTimeToLock(admin, timeMs); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1592,7 +1592,7 @@ public class DevicePolicyManager { != android.net.Proxy.PROXY_VALID) throw new IllegalArgumentException(); } - return mService.setGlobalProxy(admin, hostSpec, exclSpec, UserHandle.myUserId()); + return mService.setGlobalProxy(admin, hostSpec, exclSpec); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1758,7 +1758,7 @@ public class DevicePolicyManager { public int setStorageEncryption(ComponentName admin, boolean encrypt) { if (mService != null) { try { - return mService.setStorageEncryption(admin, encrypt, UserHandle.myUserId()); + return mService.setStorageEncryption(admin, encrypt); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -1977,7 +1977,7 @@ public class DevicePolicyManager { public void setCameraDisabled(ComponentName admin, boolean disabled) { if (mService != null) { try { - mService.setCameraDisabled(admin, disabled, UserHandle.myUserId()); + mService.setCameraDisabled(admin, disabled); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2021,7 +2021,7 @@ public class DevicePolicyManager { public void setScreenCaptureDisabled(ComponentName admin, boolean disabled) { if (mService != null) { try { - mService.setScreenCaptureDisabled(admin, UserHandle.myUserId(), disabled); + mService.setScreenCaptureDisabled(admin, disabled); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2065,7 +2065,7 @@ public class DevicePolicyManager { public void setAutoTimeRequired(ComponentName admin, boolean required) { if (mService != null) { try { - mService.setAutoTimeRequired(admin, UserHandle.myUserId(), required); + mService.setAutoTimeRequired(admin, required); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2106,7 +2106,7 @@ public class DevicePolicyManager { public void setKeyguardDisabledFeatures(ComponentName admin, int which) { if (mService != null) { try { - mService.setKeyguardDisabledFeatures(admin, which, UserHandle.myUserId()); + mService.setKeyguardDisabledFeatures(admin, which); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } @@ -2688,8 +2688,7 @@ public class DevicePolicyManager { PersistableBundle configuration) { if (mService != null) { try { - mService.setTrustAgentConfiguration(admin, target, configuration, - UserHandle.myUserId()); + mService.setTrustAgentConfiguration(admin, target, configuration); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 0ca60c0..67bca4e 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -32,34 +32,34 @@ import java.util.List; * {@hide} */ interface IDevicePolicyManager { - void setPasswordQuality(in ComponentName who, int quality, int userHandle); + void setPasswordQuality(in ComponentName who, int quality); int getPasswordQuality(in ComponentName who, int userHandle); - void setPasswordMinimumLength(in ComponentName who, int length, int userHandle); + void setPasswordMinimumLength(in ComponentName who, int length); int getPasswordMinimumLength(in ComponentName who, int userHandle); - void setPasswordMinimumUpperCase(in ComponentName who, int length, int userHandle); + void setPasswordMinimumUpperCase(in ComponentName who, int length); int getPasswordMinimumUpperCase(in ComponentName who, int userHandle); - void setPasswordMinimumLowerCase(in ComponentName who, int length, int userHandle); + void setPasswordMinimumLowerCase(in ComponentName who, int length); int getPasswordMinimumLowerCase(in ComponentName who, int userHandle); - void setPasswordMinimumLetters(in ComponentName who, int length, int userHandle); + void setPasswordMinimumLetters(in ComponentName who, int length); int getPasswordMinimumLetters(in ComponentName who, int userHandle); - void setPasswordMinimumNumeric(in ComponentName who, int length, int userHandle); + void setPasswordMinimumNumeric(in ComponentName who, int length); int getPasswordMinimumNumeric(in ComponentName who, int userHandle); - void setPasswordMinimumSymbols(in ComponentName who, int length, int userHandle); + void setPasswordMinimumSymbols(in ComponentName who, int length); int getPasswordMinimumSymbols(in ComponentName who, int userHandle); - void setPasswordMinimumNonLetter(in ComponentName who, int length, int userHandle); + void setPasswordMinimumNonLetter(in ComponentName who, int length); int getPasswordMinimumNonLetter(in ComponentName who, int userHandle); - void setPasswordHistoryLength(in ComponentName who, int length, int userHandle); + void setPasswordHistoryLength(in ComponentName who, int length); int getPasswordHistoryLength(in ComponentName who, int userHandle); - void setPasswordExpirationTimeout(in ComponentName who, long expiration, int userHandle); + void setPasswordExpirationTimeout(in ComponentName who, long expiration); long getPasswordExpirationTimeout(in ComponentName who, int userHandle); long getPasswordExpiration(in ComponentName who, int userHandle); @@ -68,33 +68,33 @@ interface IDevicePolicyManager { int getCurrentFailedPasswordAttempts(int userHandle); int getProfileWithMinimumFailedPasswordsForWipe(int userHandle); - void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, int userHandle); + void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num); int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle); - boolean resetPassword(String password, int flags, int userHandle); + boolean resetPassword(String password, int flags); - void setMaximumTimeToLock(in ComponentName who, long timeMs, int userHandle); + void setMaximumTimeToLock(in ComponentName who, long timeMs); long getMaximumTimeToLock(in ComponentName who, int userHandle); void lockNow(); void wipeData(int flags, int userHandle); - ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle); + ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList); ComponentName getGlobalProxyAdmin(int userHandle); void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo); - int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle); + int setStorageEncryption(in ComponentName who, boolean encrypt); boolean getStorageEncryption(in ComponentName who, int userHandle); int getStorageEncryptionStatus(int userHandle); - void setCameraDisabled(in ComponentName who, boolean disabled, int userHandle); + void setCameraDisabled(in ComponentName who, boolean disabled); boolean getCameraDisabled(in ComponentName who, int userHandle); - void setScreenCaptureDisabled(in ComponentName who, int userHandle, boolean disabled); + void setScreenCaptureDisabled(in ComponentName who, boolean disabled); boolean getScreenCaptureDisabled(in ComponentName who, int userHandle); - void setKeyguardDisabledFeatures(in ComponentName who, int which, int userHandle); + void setKeyguardDisabledFeatures(in ComponentName who, int which); int getKeyguardDisabledFeatures(in ComponentName who, int userHandle); void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing, int userHandle); @@ -186,7 +186,7 @@ interface IDevicePolicyManager { boolean getCrossProfileCallerIdDisabledForUser(int userId); void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent, - in PersistableBundle args, int userId); + in PersistableBundle args); List<PersistableBundle> getTrustAgentConfiguration(in ComponentName admin, in ComponentName agent, int userId); @@ -194,7 +194,7 @@ interface IDevicePolicyManager { boolean removeCrossProfileWidgetProvider(in ComponentName admin, String packageName); List<String> getCrossProfileWidgetProviders(in ComponentName admin); - void setAutoTimeRequired(in ComponentName who, int userHandle, boolean required); + void setAutoTimeRequired(in ComponentName who, boolean required); boolean getAutoTimeRequired(); boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle); diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 0450150..767f59e 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.media.AudioManager; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; @@ -415,9 +416,16 @@ public final class BluetoothA2dp implements BluetoothProfile { } /** - * Tells remote device to adjust volume. Only if absolute volume is supported. + * Tells remote device to adjust volume. Only if absolute volume is + * supported. Uses the following values: + * <ul> + * <li>{@link AudioManager#ADJUST_LOWER}</li> + * <li>{@link AudioManager#ADJUST_RAISE}</li> + * <li>{@link AudioManager#ADJUST_MUTE}</li> + * <li>{@link AudioManager#ADJUST_UNMUTE}</li> + * </ul> * - * @param direction 1 to increase volume, or -1 to decrease volume + * @param direction One of the supported adjust values. * @hide */ public void adjustAvrcpAbsoluteVolume(int direction) { diff --git a/core/java/android/midi/MidiDevice.java b/core/java/android/midi/MidiDevice.java index 8aaa86d..b91aedf 100644 --- a/core/java/android/midi/MidiDevice.java +++ b/core/java/android/midi/MidiDevice.java @@ -30,6 +30,7 @@ import java.util.ArrayList; * This class is used for sending and receiving data to and from an MIDI device * Instances of this class are created by {@link MidiManager#openDevice}. * + * CANDIDATE FOR PUBLIC API * @hide */ public final class MidiDevice { diff --git a/core/java/android/midi/MidiDeviceInfo.java b/core/java/android/midi/MidiDeviceInfo.java index 5cf62b5..dde2669 100644 --- a/core/java/android/midi/MidiDeviceInfo.java +++ b/core/java/android/midi/MidiDeviceInfo.java @@ -28,6 +28,7 @@ import android.os.Parcelable; * This class is just an immutable object to encapsulate the MIDI device description. * Use the MidiDevice class to actually communicate with devices. * + * CANDIDATE FOR PUBLIC API * @hide */ public class MidiDeviceInfo implements Parcelable { @@ -45,7 +46,7 @@ public class MidiDeviceInfo implements Parcelable { public static final int TYPE_VIRTUAL = 2; private final int mType; // USB or virtual - private final int mId; // unique ID generated by MidiService + private final int mId; // unique ID generated by MidiService private final int mInputPortCount; private final int mOutputPortCount; private final Bundle mProperties; diff --git a/core/java/android/midi/MidiDeviceServer.java b/core/java/android/midi/MidiDeviceServer.java index ccb2e0c..7499934 100644 --- a/core/java/android/midi/MidiDeviceServer.java +++ b/core/java/android/midi/MidiDeviceServer.java @@ -25,7 +25,14 @@ import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; -/** @hide */ +/** + * This class is used to provide the implemention of MIDI device. + * Applications may call {@link MidiManager#createDeviceServer} + * to create an instance of this class to implement a virtual MIDI device. + * + * CANDIDATE FOR PUBLIC API + * @hide + */ public final class MidiDeviceServer implements Closeable { private static final String TAG = "MidiDeviceServer"; diff --git a/core/java/android/midi/MidiInputPort.java b/core/java/android/midi/MidiInputPort.java index 88ace5f..51c47dd 100644 --- a/core/java/android/midi/MidiInputPort.java +++ b/core/java/android/midi/MidiInputPort.java @@ -26,6 +26,7 @@ import java.io.IOException; /** * This class is used for sending data to a port on a MIDI device * + * CANDIDATE FOR PUBLIC API * @hide */ public class MidiInputPort extends MidiPort implements MidiReceiver { diff --git a/core/java/android/midi/MidiManager.java b/core/java/android/midi/MidiManager.java index 2c1c7bf..8aa8395 100644 --- a/core/java/android/midi/MidiManager.java +++ b/core/java/android/midi/MidiManager.java @@ -35,6 +35,7 @@ import java.util.HashMap; * {@samplecode * MidiManager manager = (MidiManager) getSystemService(Context.MIDI_SERVICE);} * + * CANDIDATE FOR PUBLIC API * @hide */ public class MidiManager { @@ -184,7 +185,7 @@ public class MidiManager { * @param properties a {@link android.os.Bundle} containing properties describing the device * @param isPrivate true if this device should only be visible and accessible to apps * with the same UID as the caller - * @return a {@link MidiVirtualDevice} object to locally represent the device + * @return a {@link MidiDeviceServer} object to locally represent the device */ public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts, Bundle properties, boolean isPrivate) { diff --git a/core/java/android/midi/MidiOutputPort.java b/core/java/android/midi/MidiOutputPort.java index 00b7bad..332b431 100644 --- a/core/java/android/midi/MidiOutputPort.java +++ b/core/java/android/midi/MidiOutputPort.java @@ -26,8 +26,9 @@ import java.io.IOException; import java.util.ArrayList; /** - * This class is used for receiving data to a port on a MIDI device + * This class is used for receiving data from a port on a MIDI device * + * CANDIDATE FOR PUBLIC API * @hide */ public class MidiOutputPort extends MidiPort implements MidiSender { @@ -85,8 +86,9 @@ public class MidiOutputPort extends MidiPort implements MidiSender { } } } catch (IOException e) { - Log.e(TAG, "read failed"); // report I/O failure + Log.e(TAG, "read failed"); + } finally { IoUtils.closeQuietly(mInputStream); onIOException(); } diff --git a/core/java/android/midi/MidiPort.java b/core/java/android/midi/MidiPort.java index 44d1a88..7512a90 100644 --- a/core/java/android/midi/MidiPort.java +++ b/core/java/android/midi/MidiPort.java @@ -24,6 +24,7 @@ import java.io.Closeable; * This class represents a MIDI input or output port. * Base class for {@link MidiInputPort} and {@link MidiOutputPort} * + * CANDIDATE FOR PUBLIC API * @hide */ abstract public class MidiPort implements Closeable { diff --git a/core/java/android/midi/MidiReceiver.java b/core/java/android/midi/MidiReceiver.java index a4e1a10..fdfe51a 100644 --- a/core/java/android/midi/MidiReceiver.java +++ b/core/java/android/midi/MidiReceiver.java @@ -19,13 +19,14 @@ package android.midi; import java.io.IOException; /** - * Interface for receiving events from a MIDI device. + * Interface for receiving data from a MIDI device. * + * CANDIDATE FOR PUBLIC API * @hide */ public interface MidiReceiver { /** - * Called to pass a MIDI event to the receiver. + * Called to pass MIDI data to the receiver. * * NOTE: the msg array parameter is only valid within the context of this call. * The msg bytes should be copied by the receiver rather than retaining a reference @@ -33,9 +34,9 @@ public interface MidiReceiver { * Also, modifying the contents of the msg array parameter may result in other receivers * in the same application receiving incorrect values in their onPost() method. * - * @param msg a byte array containing the MIDI message - * @param offset the offset of the first byte of the message in the byte array - * @param count the number of bytes in the message + * @param msg a byte array containing the MIDI data + * @param offset the offset of the first byte of the data in the byte array + * @param count the number of bytes of MIDI data in the array * @param timestamp the timestamp of the message (based on {@link java.lang.System#nanoTime} * @throws IOException */ diff --git a/core/java/android/midi/MidiSender.java b/core/java/android/midi/MidiSender.java index 7958a06..2b7afad 100644 --- a/core/java/android/midi/MidiSender.java +++ b/core/java/android/midi/MidiSender.java @@ -20,6 +20,7 @@ package android.midi; * Interface provided by a device to allow attaching * MidiReceivers to a MIDI device. * + * CANDIDATE FOR PUBLIC API * @hide */ public interface MidiSender { diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java index 365f2b6..37ee961 100644 --- a/core/java/android/net/StaticIpConfiguration.java +++ b/core/java/android/net/StaticIpConfiguration.java @@ -188,6 +188,7 @@ public class StaticIpConfiguration implements Parcelable { for (InetAddress dnsServer : dnsServers) { NetworkUtils.parcelInetAddress(dest, dnsServer, flags); } + dest.writeString(domains); } protected static void readFromParcel(StaticIpConfiguration s, Parcel in) { @@ -198,5 +199,6 @@ public class StaticIpConfiguration implements Parcelable { for (int i = 0; i < size; i++) { s.dnsServers.add(NetworkUtils.unparcelInetAddress(in)); } + s.domains = in.readString(); } } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 7dd559c..cd86a3c 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -2194,6 +2194,16 @@ public final class ContactsContract { public static final String CONTACT_ID = "contact_id"; /** + * Persistent unique id for each raw_contact within its account. + * This id is provided by its own data source, and can be used to backup metadata + * to the server. + * This should be unique within each set of account_name/account_type/data_set + * + * @hide + */ + public static final String BACKUP_ID = "backup_id"; + + /** * The data set within the account that this row belongs to. This allows * multiple sync adapters for the same account type to distinguish between * each others' data. @@ -3986,6 +3996,13 @@ public final class ContactsContract { public static final String MIMETYPE = "mimetype"; /** + * Hash id on the data fields, used for backup and restore. + * + * @hide + */ + public static final String HASH_ID = "hash_id"; + + /** * A reference to the {@link RawContacts#_ID} * that this data belongs to. */ diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 259367e..19c9271 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -14885,10 +14885,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void setDisplayListProperties(RenderNode renderNode) { if (renderNode != null) { renderNode.setHasOverlappingRendering(hasOverlappingRendering()); - if (mParent instanceof ViewGroup) { - renderNode.setClipToBounds( - (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0); - } + renderNode.setClipToBounds(mParent instanceof ViewGroup + && ((ViewGroup) mParent).getClipChildren()); + float alpha = 1; if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 151ff83..15e7060 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -472,8 +472,10 @@ public final class ViewRootImpl implements ViewParent, // Compute surface insets required to draw at specified Z value. // TODO: Use real shadow insets for a constant max Z. - final int surfaceInset = (int) Math.ceil(view.getZ() * 2); - attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset); + if (!attrs.hasManualSurfaceInsets) { + final int surfaceInset = (int) Math.ceil(view.getZ() * 2); + attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset); + } CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo(); mTranslator = compatibilityInfo.getTranslator(); @@ -760,6 +762,7 @@ public final class ViewRootImpl implements ViewParent, final int oldInsetRight = mWindowAttributes.surfaceInsets.right; final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom; final int oldSoftInputMode = mWindowAttributes.softInputMode; + final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets; // Keep track of the actual window flags supplied by the client. mClientWindowLayoutFlags = attrs.flags; @@ -786,6 +789,7 @@ public final class ViewRootImpl implements ViewParent, // Restore old surface insets. mWindowAttributes.surfaceInsets.set( oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom); + mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets; applyKeepScreenOnFlag(mWindowAttributes); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 12b310f..740cb5d 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1325,6 +1325,16 @@ public interface WindowManager extends ViewManager { * @hide */ public final Rect surfaceInsets = new Rect(); + + /** + * Whether the surface insets have been manually set. When set to + * {@code false}, the view root will automatically determine the + * appropriate surface insets. + * + * @see #surfaceInsets + * @hide + */ + public boolean hasManualSurfaceInsets; /** * The desired bitmap format. May be one of the constants in @@ -1621,6 +1631,7 @@ public interface WindowManager extends ViewManager { out.writeInt(surfaceInsets.top); out.writeInt(surfaceInsets.right); out.writeInt(surfaceInsets.bottom); + out.writeInt(hasManualSurfaceInsets ? 1 : 0); out.writeInt(needsMenuKey); } @@ -1669,6 +1680,7 @@ public interface WindowManager extends ViewManager { surfaceInsets.top = in.readInt(); surfaceInsets.right = in.readInt(); surfaceInsets.bottom = in.readInt(); + hasManualSurfaceInsets = in.readInt() != 0; needsMenuKey = in.readInt(); } @@ -1851,6 +1863,11 @@ public interface WindowManager extends ViewManager { changes |= SURFACE_INSETS_CHANGED; } + if (hasManualSurfaceInsets != o.hasManualSurfaceInsets) { + hasManualSurfaceInsets = o.hasManualSurfaceInsets; + changes |= SURFACE_INSETS_CHANGED; + } + if (needsMenuKey != o.needsMenuKey) { needsMenuKey = o.needsMenuKey; changes |= NEEDS_MENU_KEY_CHANGED; @@ -1959,8 +1976,11 @@ public interface WindowManager extends ViewManager { if (userActivityTimeout >= 0) { sb.append(" userActivityTimeout=").append(userActivityTimeout); } - if (!surfaceInsets.equals(Insets.NONE)) { + if (!surfaceInsets.equals(Insets.NONE) || hasManualSurfaceInsets) { sb.append(" surfaceInsets=").append(surfaceInsets); + if (hasManualSurfaceInsets) { + sb.append(" (manual)"); + } } if (needsMenuKey != NEEDS_MENU_UNSET) { sb.append(" needsMenuKey="); diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index f5cd915..7cf3eed 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -27,6 +27,11 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.os.IBinder; +import android.transition.Transition; +import android.transition.Transition.EpicenterCallback; +import android.transition.TransitionInflater; +import android.transition.TransitionManager; +import android.transition.TransitionSet; import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; @@ -39,12 +44,13 @@ import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.WindowManager; import java.lang.ref.WeakReference; +import java.util.List; /** * <p>A popup window that can be used to display an arbitrary view. The popup * window is a floating container that appears on top of the current * activity.</p> - * + * * @see android.widget.AutoCompleteTextView * @see android.widget.Spinner */ @@ -56,7 +62,7 @@ public class PopupWindow { * it doesn't. */ public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; - + /** * Mode for {@link #setInputMethodMode(int)}: this popup always needs to * work with an input method, regardless of whether it is focusable. This @@ -64,7 +70,7 @@ public class PopupWindow { * the input method while it is shown. */ public static final int INPUT_METHOD_NEEDED = 1; - + /** * Mode for {@link #setInputMethodMode(int)}: this popup never needs to * work with an input method, regardless of whether it is focusable. This @@ -75,14 +81,32 @@ public class PopupWindow { private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START; + /** + * Default animation style indicating that separate animations should be + * used for top/bottom anchoring states. + */ + private static final int ANIMATION_STYLE_DEFAULT = -1; + + private final int[] mDrawingLocation = new int[2]; + private final int[] mScreenLocation = new int[2]; + private final Rect mTempRect = new Rect(); + private final Rect mAnchorBounds = new Rect(); + private Context mContext; private WindowManager mWindowManager; - + private boolean mIsShowing; private boolean mIsDropdown; + /** View that handles event dispatch and content transitions. */ + private PopupDecorView mDecorView; + + /** View that holds the popup background. May be the content view. */ + private View mBackgroundView; + + /** The contents of the popup. */ private View mContentView; - private View mPopupView; + private boolean mFocusable; private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; @@ -112,49 +136,52 @@ public class PopupWindow { private float mElevation; - private int[] mDrawingLocation = new int[2]; - private int[] mScreenLocation = new int[2]; - private Rect mTempRect = new Rect(); - private Drawable mBackground; private Drawable mAboveAnchorBackgroundDrawable; private Drawable mBelowAnchorBackgroundDrawable; - // Temporary animation centers. Should be moved into window params? - private int mAnchorRelativeX; - private int mAnchorRelativeY; + private Transition mEnterTransition; + private Transition mExitTransition; private boolean mAboveAnchor; private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; - + private OnDismissListener mOnDismissListener; private boolean mIgnoreCheekPress = false; - private int mAnimationStyle = -1; - + private int mAnimationStyle = ANIMATION_STYLE_DEFAULT; + private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] { com.android.internal.R.attr.state_above_anchor }; private WeakReference<View> mAnchor; - private final OnScrollChangedListener mOnScrollChangedListener = - new OnScrollChangedListener() { - @Override - public void onScrollChanged() { - final View anchor = mAnchor != null ? mAnchor.get() : null; - if (anchor != null && mPopupView != null) { - final WindowManager.LayoutParams p = (WindowManager.LayoutParams) - mPopupView.getLayoutParams(); - - updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, - mAnchoredGravity)); - update(p.x, p.y, -1, -1, true); - } + private final EpicenterCallback mEpicenterCallback = new EpicenterCallback() { + @Override + public Rect onGetEpicenter(Transition transition) { + return mAnchorBounds; + } + }; + + private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() { + @Override + public void onScrollChanged() { + final View anchor = mAnchor != null ? mAnchor.get() : null; + if (anchor != null && mDecorView != null) { + final WindowManager.LayoutParams p = (WindowManager.LayoutParams) + mDecorView.getLayoutParams(); + + updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff, + mAnchoredGravity)); + update(p.x, p.y, -1, -1, true); } - }; + } + }; - private int mAnchorXoff, mAnchorYoff, mAnchoredGravity; + private int mAnchorXoff; + private int mAnchorYoff; + private int mAnchoredGravity; private boolean mOverlapAnchor; private boolean mPopupViewInitialLayoutDirectionInherited; @@ -185,10 +212,10 @@ public class PopupWindow { public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - + /** * <p>Create a new, empty, non focusable popup window of dimension (0,0).</p> - * + * * <p>The popup does not provide a background.</p> */ public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -201,11 +228,34 @@ public class PopupWindow { mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0); mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false); - final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1); - mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle; + // Preserve default behavior from Gingerbread. If the animation is + // undefined or explicitly specifies the Gingerbread animation style, + // use a sentinel value. + if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) { + final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0); + if (animStyle == R.style.Animation_PopupWindow) { + mAnimationStyle = ANIMATION_STYLE_DEFAULT; + } else { + mAnimationStyle = animStyle; + } + } else { + mAnimationStyle = ANIMATION_STYLE_DEFAULT; + } + + final Transition enterTransition = getTransition(a.getResourceId( + R.styleable.PopupWindow_popupEnterTransition, 0)); + final Transition exitTransition; + if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) { + exitTransition = getTransition(a.getResourceId( + R.styleable.PopupWindow_popupExitTransition, 0)); + } else { + exitTransition = enterTransition == null ? null : enterTransition.clone(); + } a.recycle(); + setEnterTransition(enterTransition); + setExitTransition(exitTransition); setBackgroundDrawable(bg); } @@ -286,6 +336,37 @@ public class PopupWindow { setFocusable(focusable); } + public void setEnterTransition(Transition enterTransition) { + mEnterTransition = enterTransition; + + if (mEnterTransition != null) { + mEnterTransition.setEpicenterCallback(mEpicenterCallback); + } + } + + public void setExitTransition(Transition exitTransition) { + mExitTransition = exitTransition; + + if (mExitTransition != null) { + mExitTransition.setEpicenterCallback(mEpicenterCallback); + } + } + + private Transition getTransition(int resId) { + if (resId != 0 && resId != R.transition.no_transition) { + final TransitionInflater inflater = TransitionInflater.from(mContext); + final Transition transition = inflater.inflateTransition(resId); + if (transition != null) { + final boolean isEmpty = transition instanceof TransitionSet + && ((TransitionSet) transition).getTransitionCount() == 0; + if (!isEmpty) { + return transition; + } + } + } + return null; + } + /** * Return the drawable used as the popup window's background. * @@ -379,7 +460,7 @@ public class PopupWindow { * Set the flag on popup to ignore cheek press events; by default this flag * is set to false * which means the popup will not ignore cheek press dispatch events. - * + * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to one of * the {@link #update()} methods.</p> @@ -389,7 +470,7 @@ public class PopupWindow { public void setIgnoreCheekPress() { mIgnoreCheekPress = true; } - + /** * <p>Change the animation style resource for this popup.</p> @@ -401,13 +482,13 @@ public class PopupWindow { * @param animationStyle animation style to use when the popup appears * and disappears. Set to -1 for the default animation, 0 for no * animation, or a resource identifier for an explicit animation. - * + * * @see #update() */ public void setAnimationStyle(int animationStyle) { mAnimationStyle = animationStyle; } - + /** * <p>Return the view used as the content of the popup window.</p> * @@ -491,7 +572,7 @@ public class PopupWindow { * @param focusable true if the popup should grab focus, false otherwise. * * @see #isFocusable() - * @see #isShowing() + * @see #isShowing() * @see #update() */ public void setFocusable(boolean focusable) { @@ -500,23 +581,23 @@ public class PopupWindow { /** * Return the current value in {@link #setInputMethodMode(int)}. - * + * * @see #setInputMethodMode(int) */ public int getInputMethodMode() { return mInputMethodMode; - + } - + /** * Control how the popup operates with an input method: one of * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, * or {@link #INPUT_METHOD_NOT_NEEDED}. - * + * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to one of * the {@link #update()} methods.</p> - * + * * @see #getInputMethodMode() * @see #update() */ @@ -547,12 +628,12 @@ public class PopupWindow { public int getSoftInputMode() { return mSoftInputMode; } - + /** * <p>Indicates whether the popup window receives touch events.</p> - * + * * @return true if the popup is touchable, false otherwise - * + * * @see #setTouchable(boolean) */ public boolean isTouchable() { @@ -571,7 +652,7 @@ public class PopupWindow { * @param touchable true if the popup should receive touch events, false otherwise * * @see #isTouchable() - * @see #isShowing() + * @see #isShowing() * @see #update() */ public void setTouchable(boolean touchable) { @@ -581,9 +662,9 @@ public class PopupWindow { /** * <p>Indicates whether the popup window will be informed of touch events * outside of its window.</p> - * + * * @return true if the popup is outside touchable, false otherwise - * + * * @see #setOutsideTouchable(boolean) */ public boolean isOutsideTouchable() { @@ -604,7 +685,7 @@ public class PopupWindow { * touch events, false otherwise * * @see #isOutsideTouchable() - * @see #isShowing() + * @see #isShowing() * @see #update() */ public void setOutsideTouchable(boolean touchable) { @@ -613,9 +694,9 @@ public class PopupWindow { /** * <p>Indicates whether clipping of the popup window is enabled.</p> - * + * * @return true if the clipping is enabled, false otherwise - * + * * @see #setClippingEnabled(boolean) */ public boolean isClippingEnabled() { @@ -626,13 +707,13 @@ public class PopupWindow { * <p>Allows the popup window to extend beyond the bounds of the screen. By default the * window is clipped to the screen boundaries. Setting this to false will allow windows to be * accurately positioned.</p> - * + * * <p>If the popup is showing, calling this method will take effect only * the next time the popup is shown or through a manual call to one of * the {@link #update()} methods.</p> * * @param enabled false if the window should be allowed to extend outside of the screen - * @see #isShowing() + * @see #isShowing() * @see #isClippingEnabled() * @see #update() */ @@ -660,12 +741,12 @@ public class PopupWindow { void setAllowScrollingAnchorParent(boolean enabled) { mAllowScrollingAnchorParent = enabled; } - + /** * <p>Indicates whether the popup window supports splitting touches.</p> - * + * * @return true if the touch splitting is enabled, false otherwise - * + * * @see #setSplitTouchEnabled(boolean) */ public boolean isSplitTouchEnabled() { @@ -794,7 +875,7 @@ public class PopupWindow { * window manager by the popup. By default these are 0, meaning that * the current width or height is requested as an explicit size from * the window manager. You can supply - * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or + * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure * spec supplied instead, replacing the absolute width and height that * has been set in the popup.</p> @@ -815,7 +896,7 @@ public class PopupWindow { mWidthMode = widthSpec; mHeightMode = heightSpec; } - + /** * <p>Return this popup's height MeasureSpec</p> * @@ -836,7 +917,7 @@ public class PopupWindow { * @param height the height MeasureSpec of the popup * * @see #getHeight() - * @see #isShowing() + * @see #isShowing() */ public void setHeight(int height) { mHeight = height; @@ -847,7 +928,7 @@ public class PopupWindow { * * @return the width MeasureSpec of the popup * - * @see #setWidth(int) + * @see #setWidth(int) */ public int getWidth() { return mWidth; @@ -913,7 +994,7 @@ public class PopupWindow { * a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying * <code>Gravity.LEFT | Gravity.TOP</code>. * </p> - * + * * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from * @param gravity the gravity which controls the placement of the popup window * @param x the popup's x location offset @@ -946,7 +1027,7 @@ public class PopupWindow { WindowManager.LayoutParams p = createPopupLayout(token); p.windowAnimations = computeAnimationResource(); - + preparePopup(p); if (gravity == Gravity.NO_GRAVITY) { gravity = Gravity.TOP | Gravity.START; @@ -1049,12 +1130,12 @@ public class PopupWindow { // do the job. if (mAboveAnchorBackgroundDrawable != null) { if (mAboveAnchor) { - mPopupView.setBackground(mAboveAnchorBackgroundDrawable); + mDecorView.setBackground(mAboveAnchorBackgroundDrawable); } else { - mPopupView.setBackground(mBelowAnchorBackgroundDrawable); + mDecorView.setBackground(mBelowAnchorBackgroundDrawable); } } else { - mPopupView.refreshDrawableState(); + mDecorView.refreshDrawableState(); } } } @@ -1089,36 +1170,79 @@ public class PopupWindow { + "calling setContentView() before attempting to show the popup."); } + // When a background is available, we embed the content view within + // another view that owns the background drawable. if (mBackground != null) { - final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); - int height = ViewGroup.LayoutParams.MATCH_PARENT; - if (layoutParams != null && - layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { - height = ViewGroup.LayoutParams.WRAP_CONTENT; - } - - // when a background is available, we embed the content view - // within another view that owns the background drawable - PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); - PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, height - ); - popupViewContainer.setBackground(mBackground); - popupViewContainer.addView(mContentView, listParams); - - mPopupView = popupViewContainer; + mBackgroundView = createBackgroundView(mContentView); + mBackgroundView.setBackground(mBackground); } else { - mPopupView = mContentView; + mBackgroundView = mContentView; } - mPopupView.setElevation(mElevation); + mDecorView = createDecorView(mBackgroundView); + + // The background owner should be elevated so that it casts a shadow. + mBackgroundView.setElevation(mElevation); + + // We may wrap that in another view, so we'll need to manually specify + // the surface insets. + final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2); + p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset); + p.hasManualSurfaceInsets = true; + mPopupViewInitialLayoutDirectionInherited = - (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); + (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT); mPopupWidth = p.width; mPopupHeight = p.height; } /** + * Wraps a content view in a PopupViewContainer. + * + * @param contentView the content view to wrap + * @return a PopupViewContainer that wraps the content view + */ + private PopupBackgroundView createBackgroundView(View contentView) { + final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); + final int height; + if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { + height = ViewGroup.LayoutParams.WRAP_CONTENT; + } else { + height = ViewGroup.LayoutParams.MATCH_PARENT; + } + + final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext); + final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, height); + backgroundView.addView(contentView, listParams); + + return backgroundView; + } + + /** + * Wraps a content view in a FrameLayout. + * + * @param contentView the content view to wrap + * @return a FrameLayout that wraps the content view + */ + private PopupDecorView createDecorView(View contentView) { + final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); + final int height; + if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { + height = ViewGroup.LayoutParams.WRAP_CONTENT; + } else { + height = ViewGroup.LayoutParams.MATCH_PARENT; + } + + final PopupDecorView decorView = new PopupDecorView(mContext); + decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height); + decorView.setClipChildren(false); + decorView.setClipToPadding(false); + + return decorView; + } + + /** * <p>Invoke the popup window by adding the content view to the window * manager.</p> * @@ -1130,16 +1254,34 @@ public class PopupWindow { if (mContext != null) { p.packageName = mContext.getPackageName(); } - mPopupView.setFitsSystemWindows(mLayoutInsetDecor); + + final View rootView = mContentView.getRootView(); + rootView.setFitsSystemWindows(mLayoutInsetDecor); setLayoutDirectionFromAnchor(); - mWindowManager.addView(mPopupView, p); + + mWindowManager.addView(rootView, p); + + // Postpone enter transition until the scene root has been laid out. + if (mEnterTransition != null) { + mEnterTransition.addTarget(mBackgroundView); + mEnterTransition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + transition.removeTarget(mBackgroundView); + } + }); + + mDecorView.getViewTreeObserver().addOnGlobalLayoutListener( + new PostLayoutTransitionListener(mDecorView, mEnterTransition)); + } } private void setLayoutDirectionFromAnchor() { if (mAnchor != null) { View anchor = mAnchor.get(); if (anchor != null && mPopupViewInitialLayoutDirectionInherited) { - mPopupView.setLayoutDirection(anchor.getLayoutDirection()); + mDecorView.setLayoutDirection(anchor.getLayoutDirection()); } } } @@ -1224,7 +1366,7 @@ public class PopupWindow { } private int computeAnimationResource() { - if (mAnimationStyle == -1) { + if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) { if (mIsDropdown) { return mAboveAnchor ? com.android.internal.R.style.Animation_DropDownUp @@ -1243,7 +1385,7 @@ public class PopupWindow { * <p> * The height must have been set on the layout parameters prior to calling * this method. - * + * * @param anchor the view on which the popup window must be anchored * @param p the layout parameters used to display the drop down * @param xoff horizontal offset used to adjust for background padding @@ -1342,18 +1484,18 @@ public class PopupWindow { p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL; // Compute the position of the anchor relative to the popup. - mAnchorRelativeX = mDrawingLocation[0] - p.x + anchorHeight / 2; - mAnchorRelativeY = mDrawingLocation[1] - p.y + anchorWidth / 2; + mAnchorBounds.set(0, 0, anchorWidth, anchorHeight); + mAnchorBounds.offset(mDrawingLocation[0] - p.x, mDrawingLocation[1] - p.y); return onTop; } - + /** * Returns the maximum height that is available for the popup to be * completely shown. It is recommended that this height be the maximum for * the popup's height, otherwise it is possible that the popup will be * clipped. - * + * * @param anchor The view on which the popup window must be anchored. * @return The maximum available height for the popup to be completely * shown. @@ -1376,14 +1518,14 @@ public class PopupWindow { public int getMaxAvailableHeight(View anchor, int yOffset) { return getMaxAvailableHeight(anchor, yOffset, false); } - + /** * Returns the maximum height that is available for the popup to be * completely shown, optionally ignoring any bottom decorations such as * the input method. It is recommended that this height be the maximum for * the popup's height, otherwise it is possible that the popup will be * clipped. - * + * * @param anchor The view on which the popup window must be anchored. * @param yOffset y offset from the view's bottom edge * @param ignoreBottomDecorations if true, the height returned will be @@ -1391,7 +1533,7 @@ public class PopupWindow { * bottom decorations * @return The maximum available height for the popup to be completely * shown. - * + * * @hide Pending API council approval. */ public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { @@ -1400,7 +1542,7 @@ public class PopupWindow { final int[] anchorPos = mDrawingLocation; anchor.getLocationOnScreen(anchorPos); - + int bottomEdge = displayFrame.bottom; if (ignoreBottomDecorations) { Resources res = anchor.getContext().getResources(); @@ -1413,49 +1555,78 @@ public class PopupWindow { int returnedHeight = Math.max(distanceToBottom, distanceToTop); if (mBackground != null) { mBackground.getPadding(mTempRect); - returnedHeight -= mTempRect.top + mTempRect.bottom; + returnedHeight -= mTempRect.top + mTempRect.bottom; } - + return returnedHeight; } - + /** * <p>Dispose of the popup window. This method can be invoked only after * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling * this method will have no effect.</p> * - * @see #showAsDropDown(android.view.View) + * @see #showAsDropDown(android.view.View) */ public void dismiss() { - if (isShowing() && mPopupView != null) { + if (isShowing() && mDecorView != null) { mIsShowing = false; unregisterForScrollChanged(); - try { - mWindowManager.removeViewImmediate(mPopupView); - } finally { - if (mPopupView != mContentView && mPopupView instanceof ViewGroup) { - ((ViewGroup) mPopupView).removeView(mContentView); - } - mPopupView = null; + if (mExitTransition != null) { + mExitTransition.addTarget(mBackgroundView); + mExitTransition.addListener(new Transition.TransitionListenerAdapter() { + @Override + public void onTransitionEnd(Transition transition) { + transition.removeListener(this); + transition.removeTarget(mBackgroundView); - if (mOnDismissListener != null) { - mOnDismissListener.onDismiss(); - } + dismissImmediate(); + } + }); + + TransitionManager.beginDelayedTransition(mDecorView, mExitTransition); + + // Transition to invisible. + mBackgroundView.setVisibility(View.INVISIBLE); + } else { + dismissImmediate(); + } + } + } + + /** + * Removes the popup from the window manager and tears down the supporting + * view hierarchy, if necessary. + */ + private void dismissImmediate() { + try { + mWindowManager.removeViewImmediate(mDecorView); + } finally { + mDecorView.removeView(mBackgroundView); + mDecorView = null; + + if (mBackgroundView != mContentView) { + ((ViewGroup) mBackgroundView).removeView(mContentView); + } + mBackgroundView = null; + + if (mOnDismissListener != null) { + mOnDismissListener.onDismiss(); } } } /** * Sets the listener to be called when the window is dismissed. - * + * * @param onDismissListener The listener. */ public void setOnDismissListener(OnDismissListener onDismissListener) { mOnDismissListener = onDismissListener; } - + /** * Updates the state of the popup window, if it is currently being displayed, * from the currently set state. This includes: @@ -1467,12 +1638,12 @@ public class PopupWindow { if (!isShowing() || mContentView == null) { return; } - - WindowManager.LayoutParams p = (WindowManager.LayoutParams) - mPopupView.getLayoutParams(); - + + final WindowManager.LayoutParams p = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); + boolean update = false; - + final int newAnim = computeAnimationResource(); if (newAnim != p.windowAnimations) { p.windowAnimations = newAnim; @@ -1487,7 +1658,7 @@ public class PopupWindow { if (update) { setLayoutDirectionFromAnchor(); - mWindowManager.updateViewLayout(mPopupView, p); + mWindowManager.updateViewLayout(mDecorView, p); } } @@ -1500,11 +1671,11 @@ public class PopupWindow { * @param height the new height */ public void update(int width, int height) { - WindowManager.LayoutParams p = (WindowManager.LayoutParams) - mPopupView.getLayoutParams(); + final WindowManager.LayoutParams p = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); update(p.x, p.y, width, height, false); } - + /** * <p>Updates the position and the dimension of the popup window. Width and * height can be set to -1 to update location only. Calling this function @@ -1548,7 +1719,8 @@ public class PopupWindow { return; } - WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); + final WindowManager.LayoutParams p = + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); boolean update = force; @@ -1588,7 +1760,7 @@ public class PopupWindow { if (update) { setLayoutDirectionFromAnchor(); - mWindowManager.updateViewLayout(mPopupView, p); + mWindowManager.updateViewLayout(mDecorView, p); } } @@ -1655,7 +1827,7 @@ public class PopupWindow { } final WindowManager.LayoutParams p = - (WindowManager.LayoutParams) mPopupView.getLayoutParams(); + (WindowManager.LayoutParams) mDecorView.getLayoutParams(); final int x = p.x; final int y = p.y; if (updateLocation) { @@ -1694,7 +1866,7 @@ public class PopupWindow { private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) { unregisterForScrollChanged(); - mAnchor = new WeakReference<View>(anchor); + mAnchor = new WeakReference<>(anchor); ViewTreeObserver vto = anchor.getViewTreeObserver(); if (vto != null) { vto.addOnScrollChangedListener(mOnScrollChangedListener); @@ -1705,24 +1877,50 @@ public class PopupWindow { mAnchoredGravity = gravity; } - private class PopupViewContainer extends FrameLayout { - private static final String TAG = "PopupWindow.PopupViewContainer"; + /** + * Layout listener used to run a transition immediately after a view is + * laid out. Forces the view to transition from invisible to visible. + */ + private static class PostLayoutTransitionListener implements + ViewTreeObserver.OnGlobalLayoutListener { + private final ViewGroup mSceneRoot; + private final Transition mTransition; - public PopupViewContainer(Context context) { - super(context); + public PostLayoutTransitionListener(ViewGroup sceneRoot, Transition transition) { + mSceneRoot = sceneRoot; + mTransition = transition; } @Override - protected int[] onCreateDrawableState(int extraSpace) { - if (mAboveAnchor) { - // 1 more needed for the above anchor state - final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); - View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); - return drawableState; - } else { - return super.onCreateDrawableState(extraSpace); + public void onGlobalLayout() { + final ViewTreeObserver observer = mSceneRoot.getViewTreeObserver(); + if (observer == null) { + // View has been detached. + return; + } + + observer.removeOnGlobalLayoutListener(this); + + // Set all targets to be initially invisible. + final List<View> targets = mTransition.getTargets(); + final int N = targets.size(); + for (int i = 0; i < N; i++) { + targets.get(i).setVisibility(View.INVISIBLE); + } + + TransitionManager.beginDelayedTransition(mSceneRoot, mTransition); + + // Transition targets to visible. + for (int i = 0; i < N; i++) { + targets.get(i).setVisibility(View.VISIBLE); } } + } + + private class PopupDecorView extends FrameLayout { + public PopupDecorView(Context context) { + super(context); + } @Override public boolean dispatchKeyEvent(KeyEvent event) { @@ -1731,15 +1929,14 @@ public class PopupWindow { return super.dispatchKeyEvent(event); } - if (event.getAction() == KeyEvent.ACTION_DOWN - && event.getRepeatCount() == 0) { - KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + final KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { - KeyEvent.DispatcherState state = getKeyDispatcherState(); + final KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null && state.isTracking(event) && !event.isCanceled()) { dismiss(); return true; @@ -1763,7 +1960,7 @@ public class PopupWindow { public boolean onTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); - + if ((event.getAction() == MotionEvent.ACTION_DOWN) && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { dismiss(); @@ -1775,17 +1972,22 @@ public class PopupWindow { return super.onTouchEvent(event); } } + } + + private class PopupBackgroundView extends FrameLayout { + public PopupBackgroundView(Context context) { + super(context); + } - /** @hide */ @Override - public void sendAccessibilityEventInternal(int eventType) { - // clinets are interested in the content not the container, make it event source - if (mContentView != null) { - mContentView.sendAccessibilityEvent(eventType); + protected int[] onCreateDrawableState(int extraSpace) { + if (mAboveAnchor) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET); + return drawableState; } else { - super.sendAccessibilityEventInternal(eventType); + return super.onCreateDrawableState(extraSpace); } } } - } diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index 13d6b42..a282cf5 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -17,6 +17,7 @@ package android.widget; import android.animation.ObjectAnimator; +import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -24,6 +25,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Paint; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.Region.Op; @@ -84,7 +86,17 @@ public class Switch extends CompoundButton { private static final int MONOSPACE = 3; private Drawable mThumbDrawable; + private ColorStateList mThumbTintList = null; + private PorterDuff.Mode mThumbTintMode = null; + private boolean mHasThumbTint = false; + private boolean mHasThumbTintMode = false; + private Drawable mTrackDrawable; + private ColorStateList mTrackTintList = null; + private PorterDuff.Mode mTrackTintMode = null; + private boolean mHasTrackTint = false; + private boolean mHasTrackTintMode = false; + private int mThumbTextPadding; private int mSwitchMinWidth; private int mSwitchPadding; @@ -473,6 +485,86 @@ public class Switch extends CompoundButton { } /** + * Applies a tint to the track drawable. Does not modify the current + * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. + * <p> + * Subsequent calls to {@link #setTrackDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#Switch_trackTint + * @see #getTrackTintList() + * @see Drawable#setTintList(ColorStateList) + */ + public void setTrackTintList(@Nullable ColorStateList tint) { + mTrackTintList = tint; + mHasTrackTint = true; + + applyTrackTint(); + } + + /** + * @return the tint applied to the track drawable + * @attr ref android.R.styleable#Switch_trackTint + * @see #setTrackTintList(ColorStateList) + */ + @Nullable + public ColorStateList getTrackTintList() { + return mTrackTintList; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setTrackTintList(ColorStateList)}} to the track drawable. + * The default mode is {@link PorterDuff.Mode#SRC_IN}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#Switch_trackTintMode + * @see #getTrackTintMode() + * @see Drawable#setTintMode(PorterDuff.Mode) + */ + public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) { + mTrackTintMode = tintMode; + mHasTrackTintMode = true; + + applyTrackTint(); + } + + /** + * @return the blending mode used to apply the tint to the track + * drawable + * @attr ref android.R.styleable#Switch_trackTintMode + * @see #setTrackTintMode(PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getTrackTintMode() { + return mTrackTintMode; + } + + private void applyTrackTint() { + if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) { + mTrackDrawable = mTrackDrawable.mutate(); + + if (mHasTrackTint) { + mTrackDrawable.setTintList(mTrackTintList); + } + + if (mHasTrackTintMode) { + mTrackDrawable.setTintMode(mTrackTintMode); + } + + // The drawable (or one of its children) may not have been + // stateful before applying the tint, so let's try again. + if (mTrackDrawable.isStateful()) { + mTrackDrawable.setState(getDrawableState()); + } + } + } + + /** * Set the drawable used for the switch "thumb" - the piece that the user * can physically touch and drag along the track. * @@ -516,6 +608,86 @@ public class Switch extends CompoundButton { } /** + * Applies a tint to the thumb drawable. Does not modify the current + * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. + * <p> + * Subsequent calls to {@link #setThumbDrawable(Drawable)} will + * automatically mutate the drawable and apply the specified tint and tint + * mode using {@link Drawable#setTintList(ColorStateList)}. + * + * @param tint the tint to apply, may be {@code null} to clear tint + * + * @attr ref android.R.styleable#Switch_thumbTint + * @see #getThumbTintList() + * @see Drawable#setTintList(ColorStateList) + */ + public void setThumbTintList(@Nullable ColorStateList tint) { + mThumbTintList = tint; + mHasThumbTint = true; + + applyThumbTint(); + } + + /** + * @return the tint applied to the thumb drawable + * @attr ref android.R.styleable#Switch_thumbTint + * @see #setThumbTintList(ColorStateList) + */ + @Nullable + public ColorStateList getThumbTintList() { + return mThumbTintList; + } + + /** + * Specifies the blending mode used to apply the tint specified by + * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable. + * The default mode is {@link PorterDuff.Mode#SRC_IN}. + * + * @param tintMode the blending mode used to apply the tint, may be + * {@code null} to clear tint + * @attr ref android.R.styleable#Switch_thumbTintMode + * @see #getThumbTintMode() + * @see Drawable#setTintMode(PorterDuff.Mode) + */ + public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) { + mThumbTintMode = tintMode; + mHasThumbTintMode = true; + + applyThumbTint(); + } + + /** + * @return the blending mode used to apply the tint to the thumb + * drawable + * @attr ref android.R.styleable#Switch_thumbTintMode + * @see #setThumbTintMode(PorterDuff.Mode) + */ + @Nullable + public PorterDuff.Mode getThumbTintMode() { + return mThumbTintMode; + } + + private void applyThumbTint() { + if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) { + mThumbDrawable = mThumbDrawable.mutate(); + + if (mHasThumbTint) { + mThumbDrawable.setTintList(mThumbTintList); + } + + if (mHasThumbTintMode) { + mThumbDrawable.setTintMode(mThumbTintMode); + } + + // The drawable (or one of its children) may not have been + // stateful before applying the tint, so let's try again. + if (mThumbDrawable.isStateful()) { + mThumbDrawable.setState(getDrawableState()); + } + } + } + + /** * Specifies whether the track should be split by the thumb. When true, * the thumb's optical bounds will be clipped out of the track drawable, * then the thumb will be drawn into the resulting gap. diff --git a/core/java/com/android/internal/transition/EpicenterClipReveal.java b/core/java/com/android/internal/transition/EpicenterClipReveal.java new file mode 100644 index 0000000..d8a7f16 --- /dev/null +++ b/core/java/com/android/internal/transition/EpicenterClipReveal.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.transition; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.RectEvaluator; +import android.content.Context; +import android.graphics.Rect; +import android.transition.TransitionValues; +import android.transition.Visibility; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +/** + * EpicenterClipReveal captures the {@link View#getClipBounds()} before and + * after the scene change and animates between those and the epicenter bounds + * during a visibility transition. + */ +public class EpicenterClipReveal extends Visibility { + private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; + private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; + + public EpicenterClipReveal() {} + + public EpicenterClipReveal(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + super.captureStartValues(transitionValues); + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + super.captureEndValues(transitionValues); + captureValues(transitionValues); + } + + private void captureValues(TransitionValues values) { + final View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + final Rect clip = view.getClipBounds(); + values.values.put(PROPNAME_CLIP, clip); + + if (clip == null) { + final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); + values.values.put(PROPNAME_BOUNDS, bounds); + } + } + + @Override + public Animator onAppear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (endValues == null) { + return null; + } + + final Rect start = getEpicenter(); + final Rect end = getBestRect(endValues); + + // Prepare the view. + view.setClipBounds(start); + + return createRectAnimator(view, start, end); + } + + @Override + public Animator onDisappear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (startValues == null) { + return null; + } + + final Rect start = getBestRect(startValues); + final Rect end = getEpicenter(); + + // Prepare the view. + view.setClipBounds(start); + + return createRectAnimator(view, start, end); + } + + private Rect getBestRect(TransitionValues values) { + final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); + if (clipRect == null) { + return (Rect) values.values.get(PROPNAME_BOUNDS); + } + return clipRect; + } + + private Animator createRectAnimator(View view, Rect start, Rect end) { + final RectEvaluator evaluator = new RectEvaluator(new Rect()); + return ObjectAnimator.ofObject(view, "clipBounds", evaluator, start, end); + } +} diff --git a/core/res/res/transition/popup_window_enter.xml b/core/res/res/transition/popup_window_enter.xml new file mode 100644 index 0000000..92d4c1e --- /dev/null +++ b/core/res/res/transition/popup_window_enter.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:transitionOrdering="together"> + <transition class="com.android.internal.transition.EpicenterClipReveal" + android:interpolator="@android:interpolator/accelerate_cubic" + android:startDelay="50" + android:duration="300" /> + <fade + android:interpolator="@android:interpolator/linear" + android:duration="100" /> +</transitionSet> diff --git a/core/res/res/transition/popup_window_exit.xml b/core/res/res/transition/popup_window_exit.xml new file mode 100644 index 0000000..5cb9f0b --- /dev/null +++ b/core/res/res/transition/popup_window_exit.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<fade xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:interpolator/linear" /> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 39c42ee..559d750 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4338,6 +4338,10 @@ <attr name="popupAnimationStyle" format="reference" /> <!-- Whether the popup window should overlap its anchor view. --> <attr name="overlapAnchor" format="boolean" /> + <!-- Transition used to move views into the popup window. --> + <attr name="popupEnterTransition" format="reference" /> + <!-- Transition used to move views out of the popup window. --> + <attr name="popupExitTransition" format="reference" /> </declare-styleable> <declare-styleable name="ListPopupWindow"> <!-- Amount of pixels by which the drop down should be offset vertically. --> @@ -7257,8 +7261,34 @@ <declare-styleable name="Switch"> <!-- Drawable to use as the "thumb" that switches back and forth. --> <attr name="thumb" /> + <!-- Tint to apply to the thumb. --> + <attr name="thumbTint" /> + <!-- Blending mode used to apply the thumb tint. --> + <attr name="thumbTintMode" /> <!-- Drawable to use as the "track" that the switch thumb slides within. --> <attr name="track" format="reference" /> + <!-- Tint to apply to the track. --> + <attr name="trackTint" format="color" /> + <!-- Blending mode used to apply the track tint. --> + <attr name="trackTintMode"> + <!-- The tint is drawn on top of the drawable. + [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] --> + <enum name="src_over" value="3" /> + <!-- The tint is masked by the alpha channel of the drawable. The drawable’s + color channels are thrown out. [Sa * Da, Sc * Da] --> + <enum name="src_in" value="5" /> + <!-- The tint is drawn above the drawable, but with the drawable’s alpha + channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] --> + <enum name="src_atop" value="9" /> + <!-- Multiplies the color and alpha channels of the drawable with those of + the tint. [Sa * Da, Sc * Dc] --> + <enum name="multiply" value="14" /> + <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] --> + <enum name="screen" value="15" /> + <!-- Combines the tint and drawable color and alpha channels, clamping the + result to valid color values. Saturate(S + D) --> + <enum name="add" value="16" /> + </attr> <!-- Text to use when the switch is in the checked/"on" state. --> <attr name="textOn" /> <!-- Text to use when the switch is in the unchecked/"off" state. --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 9945c63..461f9a0 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2608,6 +2608,9 @@ =============================================================== --> <eat-comment /> + <public type="attr" name="trackTint" /> + <public type="attr" name="trackTintMode" /> + <public type="style" name="Widget.Material.Button.Colored" /> </resources> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 4329809..48645ed 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -770,6 +770,9 @@ please see styles_device_defaults.xml. <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_material</item> <item name="popupElevation">@dimen/floating_window_z</item> + <item name="popupAnimationStyle">@empty</item> + <item name="popupEnterTransition">@transition/popup_window_enter</item> + <item name="popupExitTransition">@transition/popup_window_exit</item> <item name="dropDownVerticalOffset">0dip</item> <item name="dropDownHorizontalOffset">0dip</item> <item name="overlapAnchor">true</item> @@ -780,11 +783,7 @@ please see styles_device_defaults.xml. </style> <style name="Widget.Material.Spinner.DropDown"/> - - <style name="Widget.Material.Spinner.DropDown.ActionBar"> - <item name="background">@drawable/spinner_background_material</item> - <item name="overlapAnchor">true</item> - </style> + <style name="Widget.Material.Spinner.DropDown.ActionBar" /> <style name="Widget.Material.Spinner.Underlined"> <item name="background">@drawable/spinner_textfield_background_material</item> @@ -847,7 +846,9 @@ please see styles_device_defaults.xml. <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_material</item> <item name="popupElevation">@dimen/floating_window_z</item> - <item name="popupAnimationStyle">@style/Animation.Material.Popup</item> + <item name="popupAnimationStyle">@empty</item> + <item name="popupEnterTransition">@transition/popup_window_enter</item> + <item name="popupExitTransition">@transition/popup_window_exit</item> <item name="dropDownVerticalOffset">0dip</item> <item name="dropDownHorizontalOffset">0dip</item> <item name="dropDownWidth">wrap_content</item> diff --git a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java index e7f4bad..e5a92bf 100644 --- a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java +++ b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java @@ -11,7 +11,7 @@ public class ParceledListSliceTest extends TestCase { public void testSmallList() throws Exception { final int objectCount = 100; - List<SmallObject> list = new ArrayList<>(); + List<SmallObject> list = new ArrayList<SmallObject>(); for (int i = 0; i < objectCount; i++) { list.add(new SmallObject(i * 2, (i * 2) + 1)); } @@ -20,7 +20,7 @@ public class ParceledListSliceTest extends TestCase { Parcel parcel = Parcel.obtain(); try { - parcel.writeParcelable(new ParceledListSlice<>(list), 0); + parcel.writeParcelable(new ParceledListSlice<SmallObject>(list), 0); parcel.setDataPosition(0); slice = parcel.readParcelable(getClass().getClassLoader()); } finally { @@ -56,7 +56,7 @@ public class ParceledListSliceTest extends TestCase { final int thresholdBytes = 256 * 1024; final int objectCount = thresholdBytes / measureLargeObject(); - List<LargeObject> list = new ArrayList<>(); + List<LargeObject> list = new ArrayList<LargeObject>(); for (int i = 0; i < objectCount; i++) { list.add(new LargeObject( i * 5, @@ -71,7 +71,7 @@ public class ParceledListSliceTest extends TestCase { Parcel parcel = Parcel.obtain(); try { - parcel.writeParcelable(new ParceledListSlice<>(list), 0); + parcel.writeParcelable(new ParceledListSlice<LargeObject>(list), 0); parcel.setDataPosition(0); slice = parcel.readParcelable(getClass().getClassLoader()); } finally { @@ -95,7 +95,7 @@ public class ParceledListSliceTest extends TestCase { * Test that only homogeneous elements may be unparceled. */ public void testHomogeneousElements() throws Exception { - List<BaseObject> list = new ArrayList<>(); + List<BaseObject> list = new ArrayList<BaseObject>(); list.add(new LargeObject(0, 1, 2, 3, 4)); list.add(new SmallObject(5, 6)); list.add(new SmallObject(7, 8)); diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index fa9af2a..39272b9 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -122,11 +122,6 @@ public class Picture { * @param canvas The picture is drawn to this canvas */ public void draw(Canvas canvas) { - if (canvas.isHardwareAccelerated()) { - throw new IllegalArgumentException( - "Picture playback is only supported on software canvas."); - } - if (mRecordingCanvas != null) { endRecording(); } diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk index 7c1a724..a05217f 100644 --- a/libs/hwui/Android.common.mk +++ b/libs/hwui/Android.common.mk @@ -7,11 +7,13 @@ LOCAL_CLANG_CFLAGS += \ LOCAL_SRC_FILES := \ font/CacheTexture.cpp \ font/Font.cpp \ + renderstate/Blend.cpp \ renderstate/MeshState.cpp \ renderstate/PixelBufferState.cpp \ renderstate/RenderState.cpp \ renderstate/Scissor.cpp \ renderstate/Stencil.cpp \ + renderstate/TextureState.cpp \ renderthread/CanvasContext.cpp \ renderthread/DrawFrameTask.cpp \ renderthread/EglManager.cpp \ @@ -63,6 +65,7 @@ LOCAL_SRC_FILES := \ ResourceCache.cpp \ ShadowTessellator.cpp \ SkiaCanvas.cpp \ + SkiaCanvasProxy.cpp \ SkiaShader.cpp \ Snapshot.cpp \ SpotShadow.cpp \ diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 1fb8092..cef2c84 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -49,6 +49,7 @@ Caches* Caches::sInstance = nullptr; Caches::Caches(RenderState& renderState) : patchCache(renderState) + , dither(*this) , mRenderState(&renderState) , mExtensions(Extensions::getInstance()) , mInitialized(false) { @@ -71,13 +72,8 @@ bool Caches::init() { ATRACE_NAME("Caches::init"); - glActiveTexture(gTextureUnits[0]); - mTextureUnit = 0; mRegionMesh = nullptr; - blend = false; - lastSrcMode = GL_ZERO; - lastDstMode = GL_ZERO; currentProgram = nullptr; mFunctorsCount = 0; @@ -90,8 +86,8 @@ bool Caches::init() { mInitialized = true; - resetBoundTextures(); - mPixelBufferState.reset(new PixelBufferState()); + mPixelBufferState = new PixelBufferState(); + mTextureState = new TextureState(); return true; } @@ -122,12 +118,6 @@ void Caches::initExtensions() { } void Caches::initConstraints() { - GLint maxTextureUnits; - glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits); - if (maxTextureUnits < REQUIRED_TEXTURE_UNITS_COUNT) { - ALOGW("At least %d texture units are required!", REQUIRED_TEXTURE_UNITS_COUNT); - } - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); } @@ -216,8 +206,10 @@ void Caches::terminate() { clearGarbage(); - mPixelBufferState.release(); - + delete mPixelBufferState; + mPixelBufferState = nullptr; + delete mTextureState; + mTextureState = nullptr; mInitialized = false; } @@ -348,70 +340,6 @@ void Caches::flush(FlushMode mode) { } /////////////////////////////////////////////////////////////////////////////// -// Textures -/////////////////////////////////////////////////////////////////////////////// - -void Caches::activeTexture(GLuint textureUnit) { - if (mTextureUnit != textureUnit) { - glActiveTexture(gTextureUnits[textureUnit]); - mTextureUnit = textureUnit; - } -} - -void Caches::resetActiveTexture() { - mTextureUnit = -1; -} - -void Caches::bindTexture(GLuint texture) { - if (mBoundTextures[mTextureUnit] != texture) { - glBindTexture(GL_TEXTURE_2D, texture); - mBoundTextures[mTextureUnit] = texture; - } -} - -void Caches::bindTexture(GLenum target, GLuint texture) { - if (target == GL_TEXTURE_2D) { - bindTexture(texture); - } else { - // GLConsumer directly calls glBindTexture() with - // target=GL_TEXTURE_EXTERNAL_OES, don't cache this target - // since the cached state could be stale - glBindTexture(target, texture); - } -} - -void Caches::deleteTexture(GLuint texture) { - // When glDeleteTextures() is called on a currently bound texture, - // OpenGL ES specifies that the texture is then considered unbound - // Consider the following series of calls: - // - // glGenTextures -> creates texture name 2 - // glBindTexture(2) - // glDeleteTextures(2) -> 2 is now unbound - // glGenTextures -> can return 2 again - // - // If we don't call glBindTexture(2) after the second glGenTextures - // call, any texture operation will be performed on the default - // texture (name=0) - - unbindTexture(texture); - - glDeleteTextures(1, &texture); -} - -void Caches::resetBoundTextures() { - memset(mBoundTextures, 0, REQUIRED_TEXTURE_UNITS_COUNT * sizeof(GLuint)); -} - -void Caches::unbindTexture(GLuint texture) { - for (int i = 0; i < REQUIRED_TEXTURE_UNITS_COUNT; i++) { - if (mBoundTextures[i] == texture) { - mBoundTextures[i] = 0; - } - } -} - -/////////////////////////////////////////////////////////////////////////////// // Tiling /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 8d23833..f6d3476 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -33,6 +33,7 @@ #include "PathCache.h" #include "RenderBufferCache.h" #include "renderstate/PixelBufferState.h" +#include "renderstate/TextureState.h" #include "ResourceCache.h" #include "TessellationCache.h" #include "TextDropShadowCache.h" @@ -59,20 +60,6 @@ namespace uirenderer { class GammaFontRenderer; /////////////////////////////////////////////////////////////////////////////// -// Globals -/////////////////////////////////////////////////////////////////////////////// - -// GL ES 2.0 defines that at least 16 texture units must be supported -#define REQUIRED_TEXTURE_UNITS_COUNT 3 - -// Must define as many texture units as specified by REQUIRED_TEXTURE_UNITS_COUNT -static const GLenum gTextureUnits[] = { - GL_TEXTURE0, - GL_TEXTURE1, - GL_TEXTURE2 -}; - -/////////////////////////////////////////////////////////////////////////////// // Caches /////////////////////////////////////////////////////////////////////////////// @@ -155,49 +142,6 @@ public: void deleteLayerDeferred(Layer* layer); - /** - * Activate the specified texture unit. The texture unit must - * be specified using an integer number (0 for GL_TEXTURE0 etc.) - */ - void activeTexture(GLuint textureUnit); - - /** - * Invalidate the cached value of the active texture unit. - */ - void resetActiveTexture(); - - /** - * Binds the specified texture as a GL_TEXTURE_2D texture. - * All texture bindings must be performed with this method or - * bindTexture(GLenum, GLuint). - */ - void bindTexture(GLuint texture); - - /** - * Binds the specified texture with the specified render target. - * All texture bindings must be performed with this method or - * bindTexture(GLuint). - */ - void bindTexture(GLenum target, GLuint texture); - - /** - * Deletes the specified texture and clears it from the cache - * of bound textures. - * All textures must be deleted using this method. - */ - void deleteTexture(GLuint texture); - - /** - * Signals that the cache of bound textures should be cleared. - * Other users of the context may have altered which textures are bound. - */ - void resetBoundTextures(); - - /** - * Clear the cache of bound textures. - */ - void unbindTexture(GLuint texture); - void startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard); void endTiling(); @@ -218,9 +162,6 @@ public: void registerFunctors(uint32_t functorCount); void unregisterFunctors(uint32_t functorCount); - bool blend; - GLenum lastSrcMode; - GLenum lastDstMode; Program* currentProgram; bool drawDeferDisabled; @@ -278,7 +219,8 @@ public: int propertyAmbientShadowStrength; int propertySpotShadowStrength; - PixelBufferState& pixelBuffer() { return *mPixelBufferState; } + PixelBufferState& pixelBufferState() { return *mPixelBufferState; } + TextureState& textureState() { return *mTextureState; } private: enum OverdrawColorSet { @@ -305,9 +247,8 @@ private: RenderState* mRenderState; - std::unique_ptr<PixelBufferState> mPixelBufferState; // TODO: move to RenderState - - GLuint mTextureUnit; + PixelBufferState* mPixelBufferState = nullptr; // TODO: move to RenderState + TextureState* mTextureState = nullptr; // TODO: move to RenderState Extensions& mExtensions; @@ -322,9 +263,6 @@ private: uint32_t mFunctorsCount; - // Caches texture bindings for the GL_TEXTURE_2D target - GLuint mBoundTextures[REQUIRED_TEXTURE_UNITS_COUNT]; - OverdrawColorSet mOverdrawDebugColorSet; }; // class Caches diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h index 45dc03a..7ad0683 100644 --- a/libs/hwui/Canvas.h +++ b/libs/hwui/Canvas.h @@ -42,14 +42,14 @@ public: */ static Canvas* create_canvas(SkCanvas* skiaCanvas); - // TODO: enable HWUI to either create similar canvas wrapper or subclass - // directly from Canvas - //static Canvas* create_canvas(uirenderer::Renderer* renderer); - - // TODO: this is a temporary affordance until all necessary logic can be - // moved within this interface! Further, the return value should - // NOT be unref'd and is valid until this canvas is destroyed or a - // new bitmap is set. + /** + * Provides a Skia SkCanvas interface that acts as a proxy to this Canvas. + * It is useful for testing and clients (e.g. Picture/Movie) that expect to + * draw their contents into an SkCanvas. + * + * Further, the returned SkCanvas should NOT be unref'd and is valid until + * this canvas is destroyed or a new bitmap is set. + */ virtual SkCanvas* asSkCanvas() = 0; virtual void setBitmap(SkBitmap* bitmap, bool copyState) = 0; diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index d98d744..23181bc 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -59,6 +59,7 @@ DisplayListData* DisplayListRenderer::finishRecording() { mPathMap.clear(); DisplayListData* data = mDisplayListData; mDisplayListData = nullptr; + mSkiaCanvasProxy.reset(nullptr); return data; } @@ -94,6 +95,15 @@ void DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) { mDisplayListData->functors.add(functor); } +SkCanvas* DisplayListRenderer::asSkCanvas() { + LOG_ALWAYS_FATAL_IF(!mDisplayListData, + "attempting to get an SkCanvas when we are not recording!"); + if (!mSkiaCanvasProxy) { + mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this)); + } + return mSkiaCanvasProxy.get(); +} + int DisplayListRenderer::save(SkCanvas::SaveFlags flags) { addStateOp(new (alloc()) SaveOp((int) flags)); return mState.save((int) flags); diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 527a0e5..bd0b3b7 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -28,6 +28,7 @@ #include "Canvas.h" #include "CanvasState.h" #include "DisplayList.h" +#include "SkiaCanvasProxy.h" #include "RenderNode.h" #include "Renderer.h" #include "ResourceCache.h" @@ -136,10 +137,8 @@ public: // ---------------------------------------------------------------------------- // android/graphics/Canvas interface // ---------------------------------------------------------------------------- - virtual SkCanvas* asSkCanvas() override { - LOG_ALWAYS_FATAL("DisplayListRenderer has no SkCanvas"); - return nullptr; - } + virtual SkCanvas* asSkCanvas() override; + virtual void setBitmap(SkBitmap* bitmap, bool copyState) override { LOG_ALWAYS_FATAL("DisplayListRenderer is not backed by a bitmap."); } @@ -244,6 +243,7 @@ public: private: CanvasState mState; + std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy; enum DeferredBarrierType { kBarrier_None, diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp index 12d9389..d637ec1 100644 --- a/libs/hwui/Dither.cpp +++ b/libs/hwui/Dither.cpp @@ -24,7 +24,10 @@ namespace uirenderer { // Lifecycle /////////////////////////////////////////////////////////////////////////////// -Dither::Dither(): mCaches(nullptr), mInitialized(false), mDitherTexture(0) { +Dither::Dither(Caches& caches) + : mCaches(caches) + , mInitialized(false) + , mDitherTexture(0) { } void Dither::bindDitherTexture() { @@ -32,7 +35,7 @@ void Dither::bindDitherTexture() { bool useFloatTexture = Extensions::getInstance().hasFloatTextures(); glGenTextures(1, &mDitherTexture); - mCaches->bindTexture(mDitherTexture); + mCaches.textureState().bindTexture(mDitherTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -71,13 +74,13 @@ void Dither::bindDitherTexture() { mInitialized = true; } else { - mCaches->bindTexture(mDitherTexture); + mCaches.textureState().bindTexture(mDitherTexture); } } void Dither::clear() { if (mInitialized) { - mCaches->deleteTexture(mDitherTexture); + mCaches.textureState().deleteTexture(mDitherTexture); mInitialized = false; } } @@ -87,10 +90,8 @@ void Dither::clear() { /////////////////////////////////////////////////////////////////////////////// void Dither::setupProgram(Program* program, GLuint* textureUnit) { - if (!mCaches) mCaches = &Caches::getInstance(); - GLuint textureSlot = (*textureUnit)++; - mCaches->activeTexture(textureSlot); + mCaches.textureState().activateTexture(textureSlot); bindDitherTexture(); diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h index 092ebf2..38633af 100644 --- a/libs/hwui/Dither.h +++ b/libs/hwui/Dither.h @@ -36,7 +36,7 @@ class Program; */ class Dither { public: - Dither(); + Dither(Caches& caches); void clear(); void setupProgram(Program* program, GLuint* textureUnit); @@ -44,7 +44,7 @@ public: private: void bindDitherTexture(); - Caches* mCaches; + Caches& mCaches; bool mInitialized; GLuint mDitherTexture; }; diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 193474f..6dcd3e1 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -284,7 +284,7 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t cacheWidth = cacheTexture->getWidth(); if (!cacheTexture->getPixelBuffer()) { - Caches::getInstance().activeTexture(0); + Caches::getInstance().textureState().activateTexture(0); // Large-glyph texture memory is allocated only as needed cacheTexture->allocateTexture(); } @@ -397,7 +397,7 @@ CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum for CacheTexture* cacheTexture = new CacheTexture(width, height, format, kMaxNumberOfQuads); if (allocate) { - Caches::getInstance().activeTexture(0); + Caches::getInstance().textureState().activateTexture(0); cacheTexture->allocateTexture(); cacheTexture->allocateMesh(); } @@ -443,8 +443,8 @@ void checkTextureUpdateForCache(Caches& caches, Vector<CacheTexture*>& cacheText if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) { if (cacheTexture->getTextureId() != lastTextureId) { lastTextureId = cacheTexture->getTextureId(); - caches.activeTexture(0); - caches.bindTexture(lastTextureId); + caches.textureState().activateTexture(0); + caches.textureState().bindTexture(lastTextureId); } if (cacheTexture->upload()) { @@ -470,7 +470,7 @@ void FontRenderer::checkTextureUpdate() { checkTextureUpdateForCache(caches, mRGBACacheTextures, resetPixelStore, lastTextureId); // Unbind any PBO we might have used to update textures - caches.pixelBuffer().unbind(); + caches.pixelBufferState().unbind(); // Reset to default unpack row length to avoid affecting texture // uploads in other parts of the renderer @@ -507,11 +507,11 @@ void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) { forceRebind = renderState.meshState().unbindMeshBuffer(); } - caches.activeTexture(0); + caches.textureState().activateTexture(0); first = false; } - caches.bindTexture(texture->getTextureId()); + caches.textureState().bindTexture(texture->getTextureId()); texture->setLinearFiltering(mLinearFiltering, false); TextureVertex* mesh = texture->mesh(); @@ -649,7 +649,7 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions); // Unbind any PBO we might have used - Caches::getInstance().pixelBuffer().unbind(); + Caches::getInstance().pixelBufferState().unbind(); blurImage(&dataBuffer, paddedWidth, paddedHeight, radius); } diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 0987d9b..416b0b3 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -285,7 +285,7 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* memcpy(pixels + rowBytes, pixels, rowBytes); glGenTextures(1, &texture->id); - Caches::getInstance().bindTexture(texture->id); + Caches::getInstance().textureState().bindTexture(texture->id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); if (mUseFloatTexture) { diff --git a/libs/hwui/Image.cpp b/libs/hwui/Image.cpp index edf3930..a31c546 100644 --- a/libs/hwui/Image.cpp +++ b/libs/hwui/Image.cpp @@ -39,7 +39,7 @@ Image::Image(sp<GraphicBuffer> buffer) { } else { // Create a 2D texture to sample from the EGLImage glGenTextures(1, &mTexture); - Caches::getInstance().bindTexture(mTexture); + Caches::getInstance().textureState().bindTexture(mTexture); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, mImage); GLenum status = GL_NO_ERROR; @@ -54,7 +54,7 @@ Image::~Image() { eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), mImage); mImage = EGL_NO_IMAGE_KHR; - Caches::getInstance().deleteTexture(mTexture); + Caches::getInstance().textureState().deleteTexture(mTexture); mTexture = 0; } } diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 7a4b830..7a026ef 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -134,7 +134,7 @@ bool Layer::resize(const uint32_t width, const uint32_t height) { setSize(desiredWidth, desiredHeight); if (fbo) { - caches.activeTexture(0); + caches.textureState().activateTexture(0); bindTexture(); allocateTexture(); @@ -195,7 +195,7 @@ void Layer::setColorFilter(SkColorFilter* filter) { void Layer::bindTexture() const { if (texture.id) { - caches.bindTexture(renderTarget, texture.id); + caches.textureState().bindTexture(renderTarget, texture.id); } } @@ -219,7 +219,7 @@ void Layer::deleteTexture() { } void Layer::clearTexture() { - caches.unbindTexture(texture.id); + caches.textureState().unbindTexture(texture.id); texture.id = 0; } diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 076251f..d2f9a94 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -196,7 +196,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width return nullptr; } - caches.activeTexture(0); + caches.textureState().activateTexture(0); Layer* layer = caches.layerCache.get(renderState, width, height); if (!layer) { ALOGW("Could not obtain a layer"); @@ -283,7 +283,7 @@ Layer* LayerRenderer::createTextureLayer(RenderState& renderState) { layer->region.clear(); layer->setRenderTarget(GL_NONE); // see ::updateTextureLayer() - Caches::getInstance().activeTexture(0); + Caches::getInstance().textureState().activateTexture(0); layer->generateTexture(); return layer; @@ -412,8 +412,8 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* glGenTextures(1, &texture); if ((error = glGetError()) != GL_NO_ERROR) goto error; - caches.activeTexture(0); - caches.bindTexture(texture); + caches.textureState().activateTexture(0); + caches.textureState().bindTexture(texture); glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel()); @@ -475,7 +475,7 @@ error: renderState.bindFramebuffer(previousFbo); layer->setAlpha(alpha, mode); layer->setFbo(previousLayerFbo); - caches.deleteTexture(texture); + caches.textureState().deleteTexture(texture); caches.fboCache.put(fbo); renderState.setViewport(previousViewportWidth, previousViewportHeight); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 76525e0..3ee9808 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -70,55 +70,6 @@ static GLenum getFilter(const SkPaint* paint) { // Globals /////////////////////////////////////////////////////////////////////////////// -/** - * Structure mapping Skia xfermodes to OpenGL blending factors. - */ -struct Blender { - SkXfermode::Mode mode; - GLenum src; - GLenum dst; -}; // struct Blender - -// In this array, the index of each Blender equals the value of the first -// entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode] -static const Blender gBlends[] = { - { SkXfermode::kClear_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kSrc_Mode, GL_ONE, GL_ZERO }, - { SkXfermode::kDst_Mode, GL_ZERO, GL_ONE }, - { SkXfermode::kSrcOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kDstOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE }, - { SkXfermode::kSrcIn_Mode, GL_DST_ALPHA, GL_ZERO }, - { SkXfermode::kDstIn_Mode, GL_ZERO, GL_SRC_ALPHA }, - { SkXfermode::kSrcOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, - { SkXfermode::kDstOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kSrcATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kDstATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA }, - { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kPlus_Mode, GL_ONE, GL_ONE }, - { SkXfermode::kModulate_Mode, GL_ZERO, GL_SRC_COLOR }, - { SkXfermode::kScreen_Mode, GL_ONE, GL_ONE_MINUS_SRC_COLOR } -}; - -// This array contains the swapped version of each SkXfermode. For instance -// this array's SrcOver blending mode is actually DstOver. You can refer to -// createLayer() for more information on the purpose of this array. -static const Blender gBlendsSwap[] = { - { SkXfermode::kClear_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, - { SkXfermode::kSrc_Mode, GL_ZERO, GL_ONE }, - { SkXfermode::kDst_Mode, GL_ONE, GL_ZERO }, - { SkXfermode::kSrcOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE }, - { SkXfermode::kDstOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kSrcIn_Mode, GL_ZERO, GL_SRC_ALPHA }, - { SkXfermode::kDstIn_Mode, GL_DST_ALPHA, GL_ZERO }, - { SkXfermode::kSrcOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kDstOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, - { SkXfermode::kSrcATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA }, - { SkXfermode::kDstATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, - { SkXfermode::kPlus_Mode, GL_ONE, GL_ONE }, - { SkXfermode::kModulate_Mode, GL_DST_COLOR, GL_ZERO }, - { SkXfermode::kScreen_Mode, GL_ONE_MINUS_DST_COLOR, GL_ONE } -}; /////////////////////////////////////////////////////////////////////////////// // Functions @@ -233,7 +184,7 @@ void OpenGLRenderer::prepareDirty(float left, float top, // for each layer and wait until the first drawing command // to start the frame if (currentSnapshot()->fbo == 0) { - syncState(); + mRenderState.blend().syncEnabled(); updateLayers(); } else { startFrame(); @@ -266,14 +217,6 @@ void OpenGLRenderer::clear(float left, float top, float right, float bottom, boo mRenderState.scissor().reset(); } -void OpenGLRenderer::syncState() { - if (mCaches.blend) { - glEnable(GL_BLEND); - } else { - glDisable(GL_BLEND); - } -} - void OpenGLRenderer::startTilingCurrentClip(bool opaque, bool expand) { if (!mSuppressTiling) { const Snapshot* snapshot = currentSnapshot(); @@ -558,7 +501,7 @@ void OpenGLRenderer::cancelLayerUpdate(Layer* layer) { void OpenGLRenderer::flushLayerUpdates() { ATRACE_NAME("Update HW Layers"); - syncState(); + mRenderState.blend().syncEnabled(); updateLayers(); flushLayers(); // Wait for all the layer updates to be executed @@ -755,7 +698,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto return false; } - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); Layer* layer = mCaches.layerCache.get(mRenderState, bounds.getWidth(), bounds.getHeight()); if (!layer) { return false; @@ -895,7 +838,7 @@ void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& resto mRenderState.meshState().unbindMeshBuffer(); - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); // When the layer is stored in an FBO, we can save a bit of fillrate by // drawing only the dirty region @@ -1897,13 +1840,13 @@ void OpenGLRenderer::setupDrawSimpleMesh() { } void OpenGLRenderer::setupDrawTexture(GLuint texture) { - if (texture) bindTexture(texture); + if (texture) mCaches.textureState().bindTexture(texture); mTextureUnit++; mRenderState.meshState().enableTexCoordsVertexArray(); } void OpenGLRenderer::setupDrawExternalTexture(GLuint texture) { - bindExternalTexture(texture); + mCaches.textureState().bindTexture(GL_TEXTURE_EXTERNAL_OES, texture); mTextureUnit++; mRenderState.meshState().enableTexCoordsVertexArray(); } @@ -2049,7 +1992,7 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, void OpenGLRenderer::drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount, TextureVertex* vertices, bool pureTranslate, const Rect& bounds, const SkPaint* paint) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); if (!texture) return; @@ -2080,7 +2023,7 @@ void OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { return; } - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); Texture* texture = getTexture(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); @@ -2121,7 +2064,7 @@ void OpenGLRenderer::drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int m colors = tempColors.get(); } - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); const UvMapper& mapper(getMapper(texture)); @@ -2216,7 +2159,7 @@ void OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, return; } - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); Texture* texture = getTexture(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); @@ -2316,7 +2259,7 @@ void OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Patch* mesh, } if (CC_LIKELY(mesh && mesh->verticesCount > 0)) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); @@ -2370,7 +2313,7 @@ void OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Patch* mesh, */ void OpenGLRenderer::drawPatches(const SkBitmap* bitmap, AssetAtlas::Entry* entry, TextureVertex* vertices, uint32_t indexCount, const SkPaint* paint) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); @@ -2555,7 +2498,7 @@ void OpenGLRenderer::drawRoundRect(float left, float top, float right, float bot } if (p->getPathEffect() != nullptr) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); const PathTexture* texture = mCaches.pathCache.getRoundRect( right - left, bottom - top, rx, ry, p); drawShape(left, top, texture, p); @@ -2573,7 +2516,7 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p return; } if (p->getPathEffect() != nullptr) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); const PathTexture* texture = mCaches.pathCache.getCircle(radius, p); drawShape(x - radius, y - radius, texture, p); } else { @@ -2596,7 +2539,7 @@ void OpenGLRenderer::drawOval(float left, float top, float right, float bottom, } if (p->getPathEffect() != nullptr) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); const PathTexture* texture = mCaches.pathCache.getOval(right - left, bottom - top, p); drawShape(left, top, texture, p); } else { @@ -2620,7 +2563,7 @@ void OpenGLRenderer::drawArc(float left, float top, float right, float bottom, // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180) if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != nullptr || useCenter) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); const PathTexture* texture = mCaches.pathCache.getArc(right - left, bottom - top, startAngle, sweepAngle, useCenter, p); drawShape(left, top, texture, p); @@ -2657,7 +2600,7 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, // only fill style is supported by drawConvexPath, since others have to handle joins if (p->getPathEffect() != nullptr || p->getStrokeJoin() != SkPaint::kMiter_Join || p->getStrokeMiter() != SkPaintDefaults_MiterLimit) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); const PathTexture* texture = mCaches.pathCache.getRect(right - left, bottom - top, p); drawShape(left, top, texture, p); @@ -2686,7 +2629,7 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count, const float* positions, FontRenderer& fontRenderer, int alpha, float x, float y) { - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); TextShadow textShadow; if (!getTextShadow(paint, &textShadow)) { @@ -3000,7 +2943,7 @@ void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, void OpenGLRenderer::drawPath(const SkPath* path, const SkPaint* paint) { if (mState.currentlyIgnored()) return; - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); const PathTexture* texture = mCaches.pathCache.get(path, paint); if (!texture) return; @@ -3045,7 +2988,7 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { updateLayer(layer, true); mRenderState.scissor().setEnabled(mScissorOptimizationDisabled || clipRequired); - mCaches.activeTexture(0); + mCaches.textureState().activateTexture(0); if (CC_LIKELY(!layer->region.isEmpty())) { if (layer->region.isRect()) { @@ -3484,33 +3427,16 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, description.framebufferMode = mode; description.swapSrcDst = swapSrcDst; - if (mCaches.blend) { - glDisable(GL_BLEND); - mCaches.blend = false; - } - + mRenderState.blend().disable(); return; } else { mode = SkXfermode::kSrcOver_Mode; } } - - if (!mCaches.blend) { - glEnable(GL_BLEND); - } - - GLenum sourceMode = swapSrcDst ? gBlendsSwap[mode].src : gBlends[mode].src; - GLenum destMode = swapSrcDst ? gBlendsSwap[mode].dst : gBlends[mode].dst; - - if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) { - glBlendFunc(sourceMode, destMode); - mCaches.lastSrcMode = sourceMode; - mCaches.lastDstMode = destMode; - } - } else if (mCaches.blend) { - glDisable(GL_BLEND); + mRenderState.blend().enable(mode, swapSrcDst); + } else { + mRenderState.blend().disable(); } - mCaches.blend = blend; } bool OpenGLRenderer::useProgram(Program* program) { diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 94054ff..cf6f0c8 100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -541,12 +541,6 @@ private: void discardFramebuffer(float left, float top, float right, float bottom); /** - * Ensures the state of the renderer is the same as the state of - * the GL context. - */ - void syncState(); - - /** * Tells the GPU what part of the screen is about to be redrawn. * This method will use the current layer space clip rect. * This method needs to be invoked every time getTargetFbo() is @@ -852,22 +846,6 @@ private: bool canSkipText(const SkPaint* paint) const; /** - * Binds the specified texture. The texture unit must have been selected - * prior to calling this method. - */ - inline void bindTexture(GLuint texture) { - mCaches.bindTexture(texture); - } - - /** - * Binds the specified EGLImage texture. The texture unit must have been selected - * prior to calling this method. - */ - inline void bindExternalTexture(GLuint texture) { - mCaches.bindTexture(GL_TEXTURE_EXTERNAL_OES, texture); - } - - /** * Enable or disable blending as necessary. This function sets the appropriate * blend function based on the specified xfermode. */ diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index cc7f88d..d6eff85 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -230,7 +230,7 @@ void PathCache::removeTexture(PathTexture* texture) { } if (texture->id) { - Caches::getInstance().deleteTexture(texture->id); + Caches::getInstance().textureState().deleteTexture(texture->id); } delete texture; } @@ -312,7 +312,7 @@ void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) { glGenTextures(1, &texture->id); - Caches::getInstance().bindTexture(texture->id); + Caches::getInstance().textureState().bindTexture(texture->id); // Textures are Alpha8 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp index 62eb68c..9665a68 100644 --- a/libs/hwui/PixelBuffer.cpp +++ b/libs/hwui/PixelBuffer.cpp @@ -101,9 +101,9 @@ GpuPixelBuffer::GpuPixelBuffer(GLenum format, , mCaches(Caches::getInstance()){ glGenBuffers(1, &mBuffer); - mCaches.pixelBuffer().bind(mBuffer); + mCaches.pixelBufferState().bind(mBuffer); glBufferData(GL_PIXEL_UNPACK_BUFFER, getSize(), nullptr, GL_DYNAMIC_DRAW); - mCaches.pixelBuffer().unbind(); + mCaches.pixelBufferState().unbind(); } GpuPixelBuffer::~GpuPixelBuffer() { @@ -112,7 +112,7 @@ GpuPixelBuffer::~GpuPixelBuffer() { uint8_t* GpuPixelBuffer::map(AccessMode mode) { if (mAccessMode == kAccessMode_None) { - mCaches.pixelBuffer().bind(mBuffer); + mCaches.pixelBufferState().bind(mBuffer); mMappedPointer = (uint8_t*) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, getSize(), mode); #if DEBUG_OPENGL if (!mMappedPointer) { @@ -131,7 +131,7 @@ uint8_t* GpuPixelBuffer::map(AccessMode mode) { void GpuPixelBuffer::unmap() { if (mAccessMode != kAccessMode_None) { if (mMappedPointer) { - mCaches.pixelBuffer().bind(mBuffer); + mCaches.pixelBufferState().bind(mBuffer); GLboolean status = glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); if (status == GL_FALSE) { ALOGE("Corrupted GPU pixel buffer"); @@ -148,7 +148,7 @@ uint8_t* GpuPixelBuffer::getMappedPointer() const { void GpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) { // If the buffer is not mapped, unmap() will not bind it - mCaches.pixelBuffer().bind(mBuffer); + mCaches.pixelBufferState().bind(mBuffer); unmap(); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, mFormat, GL_UNSIGNED_BYTE, reinterpret_cast<void*>(offset)); diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp new file mode 100644 index 0000000..de5f91c --- /dev/null +++ b/libs/hwui/SkiaCanvasProxy.cpp @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkiaCanvasProxy.h" + +#include <cutils/log.h> +#include <SkPatchUtils.h> + +namespace android { +namespace uirenderer { + +SkiaCanvasProxy::SkiaCanvasProxy(Canvas* canvas) + : INHERITED(canvas->width(), canvas->height()) + , mCanvas(canvas) {} + +void SkiaCanvasProxy::onDrawPaint(const SkPaint& paint) { + mCanvas->drawPaint(paint); +} + +void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + // convert the SkPoints into floats + SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + const size_t floatCount = count << 1; + const float* floatArray = &pts[0].fX; + + switch (pointMode) { + case kPoints_PointMode: { + mCanvas->drawPoints(floatArray, floatCount, paint); + break; + } + case kLines_PointMode: { + mCanvas->drawLines(floatArray, floatCount, paint); + break; + } + case kPolygon_PointMode: { + SkPaint strokedPaint(paint); + strokedPaint.setStyle(SkPaint::kStroke_Style); + + SkPath path; + for (size_t i = 0; i < count - 1; i++) { + path.moveTo(pts[i]); + path.lineTo(pts[i+1]); + this->drawPath(path, strokedPaint); + path.rewind(); + } + break; + } + default: + LOG_ALWAYS_FATAL("Unknown point type"); + } +} + +void SkiaCanvasProxy::onDrawOval(const SkRect& rect, const SkPaint& paint) { + mCanvas->drawOval(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint); +} + +void SkiaCanvasProxy::onDrawRect(const SkRect& rect, const SkPaint& paint) { + mCanvas->drawRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint); +} + +void SkiaCanvasProxy::onDrawRRect(const SkRRect& roundRect, const SkPaint& paint) { + if (!roundRect.isComplex()) { + const SkRect& rect = roundRect.rect(); + SkVector radii = roundRect.getSimpleRadii(); + mCanvas->drawRoundRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + radii.fX, radii.fY, paint); + } else { + SkPath path; + path.addRRect(roundRect); + mCanvas->drawPath(path, paint); + } +} + +void SkiaCanvasProxy::onDrawPath(const SkPath& path, const SkPaint& paint) { + mCanvas->drawPath(path, paint); +} + +void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, + const SkPaint* paint) { + mCanvas->drawBitmap(bitmap, left, top, paint); +} + +void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* srcPtr, + const SkRect& dst, const SkPaint* paint, DrawBitmapRectFlags) { + SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(bitmap.width(), bitmap.height()); + mCanvas->drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, + dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint); +} + +void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, + const SkRect& dst, const SkPaint*) { + //TODO make nine-patch drawing a method on Canvas.h + SkDEBUGFAIL("SkiaCanvasProxy::onDrawBitmapNine is not yet supported"); +} + +void SkiaCanvasProxy::onDrawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint) { + mCanvas->save(SkCanvas::kMatrixClip_SaveFlag); + mCanvas->setMatrix(SkMatrix::I()); + mCanvas->drawBitmap(bitmap, left, top, paint); + mCanvas->restore(); +} + +void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkPoint vertices[], + const SkPoint texs[], const SkColor colors[], SkXfermode*, const uint16_t indices[], + int indexCount, const SkPaint& paint) { + // convert the SkPoints into floats + SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + const int floatCount = vertexCount << 1; + const float* vArray = &vertices[0].fX; + const float* tArray = (texs) ? &texs[0].fX : NULL; + const int* cArray = (colors) ? (int*)colors : NULL; + mCanvas->drawVertices(mode, floatCount, vArray, tArray, cArray, indices, indexCount, paint); +} + +SkSurface* SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) { + SkDEBUGFAIL("SkiaCanvasProxy::onNewSurface is not supported"); + return NULL; +} + +void SkiaCanvasProxy::willSave() { + mCanvas->save(SkCanvas::kMatrixClip_SaveFlag); +} + +SkCanvas::SaveLayerStrategy SkiaCanvasProxy::willSaveLayer(const SkRect* rectPtr, + const SkPaint* paint, SaveFlags flags) { + SkRect rect; + if (rectPtr) { + rect = *rectPtr; + } else if(!mCanvas->getClipBounds(&rect)) { + rect = SkRect::MakeEmpty(); + } + mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint, flags); + return SkCanvas::kNoLayer_SaveLayerStrategy; +} + +void SkiaCanvasProxy::willRestore() { + mCanvas->restore(); +} + +void SkiaCanvasProxy::didConcat(const SkMatrix& matrix) { + mCanvas->concat(matrix); +} + +void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) { + mCanvas->setMatrix(matrix); +} + +void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) { + SkPath path; + path.addRRect(outer); + path.addRRect(inner); + path.setFillType(SkPath::kEvenOdd_FillType); + this->drawPath(path, paint); +} + +/** + * Utility class that converts the incoming text & paint from the given encoding + * into glyphIDs. + */ +class GlyphIDConverter { +public: + GlyphIDConverter(const void* text, size_t byteLength, const SkPaint& origPaint) { + paint = origPaint; + if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) { + glyphIDs = (uint16_t*)text; + count = byteLength >> 1; + } else { + storage.reset(byteLength); // ensures space for one glyph per ID given UTF8 encoding. + glyphIDs = storage.get(); + count = paint.textToGlyphs(text, byteLength, storage.get()); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + } + } + + SkPaint paint; + uint16_t* glyphIDs; + int count; +private: + SkAutoSTMalloc<32, uint16_t> storage; +}; + +void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, + const SkPaint& origPaint) { + // convert to glyphIDs if necessary + GlyphIDConverter glyphs(text, byteLength, origPaint); + + // compute the glyph positions + SkAutoSTMalloc<32, SkPoint> pointStorage(glyphs.count); + SkAutoSTMalloc<32, SkScalar> glyphWidths(glyphs.count); + glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get()); + + // compute conservative bounds + // NOTE: We could call the faster paint.getFontBounds for a less accurate, + // but even more conservative bounds if this is too slow. + SkRect bounds; + glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds); + + // adjust for non-left alignment + if (glyphs.paint.getTextAlign() != SkPaint::kLeft_Align) { + SkScalar stop = 0; + for (int i = 0; i < glyphs.count; i++) { + stop += glyphWidths[i]; + } + if (glyphs.paint.getTextAlign() == SkPaint::kCenter_Align) { + stop = SkScalarHalf(stop); + } + if (glyphs.paint.isVerticalText()) { + y -= stop; + } else { + x -= stop; + } + } + + // setup the first glyph position and adjust bounds if needed + if (mCanvas->drawTextAbsolutePos()) { + bounds.offset(x,y); + pointStorage[0].set(x, y); + } else { + pointStorage[0].set(0, 0); + } + + // setup the remaining glyph positions + if (glyphs.paint.isVerticalText()) { + for (int i = 1; i < glyphs.count; i++) { + pointStorage[i].set(x, glyphWidths[i-1] + pointStorage[i-1].fY); + } + } else { + for (int i = 1; i < glyphs.count; i++) { + pointStorage[i].set(glyphWidths[i-1] + pointStorage[i-1].fX, y); + } + } + + SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + mCanvas->drawText(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint, + x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0); +} + +void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], + const SkPaint& origPaint) { + // convert to glyphIDs if necessary + GlyphIDConverter glyphs(text, byteLength, origPaint); + + // convert to relative positions if necessary + int x, y; + const SkPoint* posArray; + SkAutoSTMalloc<32, SkPoint> pointStorage; + if (mCanvas->drawTextAbsolutePos()) { + x = 0; + y = 0; + posArray = pos; + } else { + x = pos[0].fX; + y = pos[0].fY; + posArray = pointStorage.reset(glyphs.count); + for (int i = 0; i < glyphs.count; i++) { + pointStorage[i].fX = pos[i].fX- x; + pointStorage[i].fY = pos[i].fY- y; + } + } + + // compute conservative bounds + // NOTE: We could call the faster paint.getFontBounds for a less accurate, + // but even more conservative bounds if this is too slow. + SkRect bounds; + glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds); + + SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + mCanvas->drawText(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y, + bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0); +} + +void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], + SkScalar constY, const SkPaint& paint) { + const size_t pointCount = byteLength >> 1; + SkAutoSTMalloc<32, SkPoint> storage(pointCount); + SkPoint* pts = storage.get(); + for (size_t i = 0; i < pointCount; i++) { + pts[i].set(xpos[i], constY); + } + this->onDrawPosText(text, byteLength, pts, paint); +} + +void SkiaCanvasProxy::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, + const SkMatrix* matrix, const SkPaint& origPaint) { + // convert to glyphIDs if necessary + GlyphIDConverter glyphs(text, byteLength, origPaint); + mCanvas->drawTextOnPath(glyphs.glyphIDs, glyphs.count, path, 0, 0, glyphs.paint); +} + +void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + SkDEBUGFAIL("SkiaCanvasProxy::onDrawTextBlob is not supported"); +} + +void SkiaCanvasProxy::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkXfermode* xmode, const SkPaint& paint) { + SkPatchUtils::VertexData data; + + SkMatrix matrix; + mCanvas->getMatrix(&matrix); + SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &matrix); + + // It automatically adjusts lodX and lodY in case it exceeds the number of indices. + // If it fails to generate the vertices, then we do not draw. + if (SkPatchUtils::getVertexData(&data, cubics, colors, texCoords, lod.width(), lod.height())) { + this->drawVertices(SkCanvas::kTriangles_VertexMode, data.fVertexCount, data.fPoints, + data.fTexCoords, data.fColors, xmode, data.fIndices, data.fIndexCount, + paint); + } +} + +void SkiaCanvasProxy::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle) { + mCanvas->clipRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, op); +} + +void SkiaCanvasProxy::onClipRRect(const SkRRect& roundRect, SkRegion::Op op, ClipEdgeStyle) { + SkPath path; + path.addRRect(roundRect); + mCanvas->clipPath(&path, op); +} + +void SkiaCanvasProxy::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle) { + mCanvas->clipPath(&path, op); +} + +void SkiaCanvasProxy::onClipRegion(const SkRegion& region, SkRegion::Op op) { + mCanvas->clipRegion(®ion, op); +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h new file mode 100644 index 0000000..4322fcf --- /dev/null +++ b/libs/hwui/SkiaCanvasProxy.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SkiaCanvasProxy_DEFINED +#define SkiaCanvasProxy_DEFINED + +#include <cutils/compiler.h> +#include <SkCanvas.h> + +#include "Canvas.h" + +namespace android { +namespace uirenderer { + +/** + * This class serves as a proxy between Skia's SkCanvas and Android Framework's + * Canvas. The class does not maintain any state and will pass through any request + * directly to the Canvas provided in the constructor. + * + * Upon construction it is expected that the provided Canvas has already been + * prepared for recording and will continue to be in the recording state while + * this proxy class is being used. + */ +class ANDROID_API SkiaCanvasProxy : public SkCanvas { +public: + SkiaCanvasProxy(Canvas* canvas); + virtual ~SkiaCanvasProxy() {} + +protected: + + virtual SkSurface* onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; + + virtual void willSave() override; + virtual SaveLayerStrategy willSaveLayer(const SkRect*, const SkPaint*, SaveFlags) override; + virtual void willRestore() override; + + virtual void didConcat(const SkMatrix&) override; + virtual void didSetMatrix(const SkMatrix&) override; + + virtual void onDrawPaint(const SkPaint& paint) override; + virtual void onDrawPoints(PointMode, size_t count, const SkPoint pts[], + const SkPaint&) override; + virtual void onDrawOval(const SkRect&, const SkPaint&) override; + virtual void onDrawRect(const SkRect&, const SkPaint&) override; + virtual void onDrawRRect(const SkRRect&, const SkPaint&) override; + virtual void onDrawPath(const SkPath& path, const SkPaint&) override; + virtual void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, + const SkPaint*) override; + virtual void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, + const SkPaint* paint, DrawBitmapRectFlags flags) override; + virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, + const SkRect& dst, const SkPaint*) override; + virtual void onDrawSprite(const SkBitmap&, int left, int top, + const SkPaint*) override; + virtual void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[], + const SkPoint texs[], const SkColor colors[], SkXfermode*, + const uint16_t indices[], int indexCount, + const SkPaint&) override; + + virtual void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + + virtual void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, + const SkPaint&) override; + virtual void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], + const SkPaint&) override; + virtual void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], + SkScalar constY, const SkPaint&) override; + virtual void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, + const SkMatrix* matrix, const SkPaint&) override; + virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override; + + virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkXfermode* xmode, + const SkPaint& paint) override; + + virtual void onClipRect(const SkRect&, SkRegion::Op, ClipEdgeStyle) override; + virtual void onClipRRect(const SkRRect&, SkRegion::Op, ClipEdgeStyle) override; + virtual void onClipPath(const SkPath&, SkRegion::Op, ClipEdgeStyle) override; + virtual void onClipRegion(const SkRegion&, SkRegion::Op) override; + +private: + Canvas* mCanvas; + + typedef SkCanvas INHERITED; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // SkiaCanvasProxy_DEFINED diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 2c09344..e13c861 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -57,7 +57,7 @@ static inline void bindUniformColor(int slot, uint32_t color) { } static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) { - caches->bindTexture(texture->id); + caches->textureState().bindTexture(texture->id); texture->setWrapST(wrapS, wrapT); } @@ -176,7 +176,7 @@ void SkiaLayerShader::setupProgram(Caches* caches, const mat4& modelViewMatrix, } GLuint textureSlot = (*textureUnit)++; - caches->activeTexture(textureSlot); + caches->textureState().activateTexture(textureSlot); const float width = layer->getWidth(); const float height = layer->getHeight(); @@ -270,7 +270,7 @@ void SkiaBitmapShader::setupProgram(Caches* caches, const mat4& modelViewMatrix, } GLuint textureSlot = (*textureUnit)++; - Caches::getInstance().activeTexture(textureSlot); + Caches::getInstance().textureState().activateTexture(textureSlot); BitmapShaderInfo shaderInfo; if (!bitmapShaderHelper(caches, nullptr, &shaderInfo, extensions, bitmap, xy)) { @@ -392,7 +392,7 @@ void SkiaGradientShader::setupProgram(Caches* caches, const mat4& modelViewMatri shader.asAGradient(&gradInfo); } GLuint textureSlot = (*textureUnit)++; - caches->activeTexture(textureSlot); + caches->textureState().activateTexture(textureSlot); #ifndef SK_SCALAR_IS_FLOAT #error Need to convert gradInfo.fColorOffsets to float! diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp index 4ec298d..c2e88f3 100644 --- a/libs/hwui/TextDropShadowCache.cpp +++ b/libs/hwui/TextDropShadowCache.cpp @@ -207,7 +207,7 @@ ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, glGenTextures(1, &texture->id); - caches.bindTexture(texture->id); + caches.textureState().bindTexture(texture->id); // Textures are Alpha8 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 58fd972..512f5cf 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -24,18 +24,44 @@ namespace android { namespace uirenderer { -Texture::Texture(): id(0), generation(0), blend(false), width(0), height(0), - cleanup(false), bitmapSize(0), mipMap(false), uvMapper(nullptr), isInUse(false), - mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE), - mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST), - mFirstFilter(true), mFirstWrap(true), mCaches(Caches::getInstance()) { +Texture::Texture() + : id(0) + , generation(0) + , blend(false) + , width(0) + , height(0) + , cleanup(false) + , bitmapSize(0) + , mipMap(false) + , uvMapper(nullptr) + , isInUse(false) + , mWrapS(GL_CLAMP_TO_EDGE) + , mWrapT(GL_CLAMP_TO_EDGE) + , mMinFilter(GL_NEAREST) + , mMagFilter(GL_NEAREST) + , mFirstFilter(true) + , mFirstWrap(true) + , mCaches(Caches::getInstance()) { } -Texture::Texture(Caches& caches): id(0), generation(0), blend(false), width(0), height(0), - cleanup(false), bitmapSize(0), mipMap(false), uvMapper(nullptr), isInUse(false), - mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE), - mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST), - mFirstFilter(true), mFirstWrap(true), mCaches(caches) { +Texture::Texture(Caches& caches) + : id(0) + , generation(0) + , blend(false) + , width(0) + , height(0) + , cleanup(false) + , bitmapSize(0) + , mipMap(false) + , uvMapper(nullptr) + , isInUse(false) + , mWrapS(GL_CLAMP_TO_EDGE) + , mWrapT(GL_CLAMP_TO_EDGE) + , mMinFilter(GL_NEAREST) + , mMagFilter(GL_NEAREST) + , mFirstFilter(true) + , mFirstWrap(true) + , mCaches(caches) { } void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force, @@ -48,7 +74,7 @@ void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force mWrapT = wrapT; if (bindTexture) { - mCaches.bindTexture(renderTarget, id); + mCaches.textureState().bindTexture(renderTarget, id); } glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); @@ -66,7 +92,7 @@ void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool for mMagFilter = mag; if (bindTexture) { - mCaches.bindTexture(renderTarget, id); + mCaches.textureState().bindTexture(renderTarget, id); } if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; @@ -77,7 +103,7 @@ void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool for } void Texture::deleteTexture() const { - mCaches.deleteTexture(id); + mCaches.textureState().deleteTexture(id); } }; // namespace uirenderer diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 524f206..fe8fb5b 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -296,7 +296,7 @@ void TextureCache::generateTexture(const SkBitmap* bitmap, Texture* texture, boo texture->width = bitmap->width(); texture->height = bitmap->height(); - Caches::getInstance().bindTexture(texture->id); + Caches::getInstance().textureState().bindTexture(texture->id); switch (bitmap->colorType()) { case kAlpha_8_SkColorType: diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index 128e392..53fa0dc 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -157,7 +157,7 @@ void CacheTexture::releaseTexture() { mTexture = nullptr; } if (mTextureId) { - mCaches.deleteTexture(mTextureId); + mCaches.textureState().deleteTexture(mTextureId); mTextureId = 0; } mDirty = false; @@ -169,7 +169,7 @@ void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) { mLinearFiltering = linearFiltering; const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST; - if (bind) mCaches.bindTexture(getTextureId()); + if (bind) mCaches.textureState().bindTexture(getTextureId()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); } @@ -189,7 +189,7 @@ void CacheTexture::allocateTexture() { if (!mTextureId) { glGenTextures(1, &mTextureId); - mCaches.bindTexture(mTextureId); + mCaches.textureState().bindTexture(mTextureId); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Initialize texture dimensions glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp new file mode 100644 index 0000000..3e7b721 --- /dev/null +++ b/libs/hwui/renderstate/Blend.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <renderstate/Blend.h> +#include "Program.h" + +#include "ShadowTessellator.h" + +namespace android { +namespace uirenderer { + +/** + * Structure mapping Skia xfermodes to OpenGL blending factors. + */ +struct Blender { + SkXfermode::Mode mode; + GLenum src; + GLenum dst; +}; + +// In this array, the index of each Blender equals the value of the first +// entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode] +const Blender kBlends[] = { + { SkXfermode::kClear_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kSrc_Mode, GL_ONE, GL_ZERO }, + { SkXfermode::kDst_Mode, GL_ZERO, GL_ONE }, + { SkXfermode::kSrcOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kDstOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE }, + { SkXfermode::kSrcIn_Mode, GL_DST_ALPHA, GL_ZERO }, + { SkXfermode::kDstIn_Mode, GL_ZERO, GL_SRC_ALPHA }, + { SkXfermode::kSrcOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, + { SkXfermode::kDstOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kSrcATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kDstATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA }, + { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kPlus_Mode, GL_ONE, GL_ONE }, + { SkXfermode::kModulate_Mode, GL_ZERO, GL_SRC_COLOR }, + { SkXfermode::kScreen_Mode, GL_ONE, GL_ONE_MINUS_SRC_COLOR } +}; + +// This array contains the swapped version of each SkXfermode. For instance +// this array's SrcOver blending mode is actually DstOver. You can refer to +// createLayer() for more information on the purpose of this array. +const Blender kBlendsSwap[] = { + { SkXfermode::kClear_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, + { SkXfermode::kSrc_Mode, GL_ZERO, GL_ONE }, + { SkXfermode::kDst_Mode, GL_ONE, GL_ZERO }, + { SkXfermode::kSrcOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE }, + { SkXfermode::kDstOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kSrcIn_Mode, GL_ZERO, GL_SRC_ALPHA }, + { SkXfermode::kDstIn_Mode, GL_DST_ALPHA, GL_ZERO }, + { SkXfermode::kSrcOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kDstOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO }, + { SkXfermode::kSrcATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA }, + { SkXfermode::kDstATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }, + { SkXfermode::kPlus_Mode, GL_ONE, GL_ONE }, + { SkXfermode::kModulate_Mode, GL_DST_COLOR, GL_ZERO }, + { SkXfermode::kScreen_Mode, GL_ONE_MINUS_DST_COLOR, GL_ONE } +}; + +Blend::Blend() + : mEnabled(false) + , mSrcMode(GL_ZERO) + , mDstMode(GL_ZERO) { + // gl blending off by default +} + +void Blend::enable(SkXfermode::Mode mode, bool swapSrcDst) { + // enable + if (!mEnabled) { + glEnable(GL_BLEND); + mEnabled = true; + } + + // select blend mode + GLenum sourceMode = swapSrcDst ? kBlendsSwap[mode].src : kBlends[mode].src; + GLenum destMode = swapSrcDst ? kBlendsSwap[mode].dst : kBlends[mode].dst; + + if (sourceMode != mSrcMode || destMode != mSrcMode) { + glBlendFunc(sourceMode, destMode); + mSrcMode = sourceMode; + mDstMode = destMode; + } +} + +void Blend::disable() { + if (mEnabled) { + glDisable(GL_BLEND); + mEnabled = false; + } +} + +void Blend::invalidate() { + syncEnabled(); + mSrcMode = mDstMode = GL_ZERO; +} + +void Blend::syncEnabled() { + if (mEnabled) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } +} + +} /* namespace uirenderer */ +} /* namespace android */ + diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h new file mode 100644 index 0000000..b82b477 --- /dev/null +++ b/libs/hwui/renderstate/Blend.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef RENDERSTATE_BLEND_H +#define RENDERSTATE_BLEND_H + +#include "Vertex.h" + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <SkXfermode.h> +#include <memory> + +namespace android { +namespace uirenderer { + +class Blend { + friend class RenderState; +public: + void enable(SkXfermode::Mode mode, bool swapSrcDst); + void disable(); + void syncEnabled(); +private: + Blend(); + void invalidate(); + bool mEnabled; + GLenum mSrcMode; + GLenum mDstMode; +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif // RENDERSTATE_BLEND_H diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index e4c8745..58ec321 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -23,10 +23,6 @@ namespace uirenderer { RenderState::RenderState(renderthread::RenderThread& thread) : mRenderThread(thread) - , mCaches(nullptr) - , mMeshState(nullptr) - , mScissor(nullptr) - , mStencil(nullptr) , mViewportWidth(0) , mViewportHeight(0) , mFramebuffer(0) { @@ -34,19 +30,22 @@ RenderState::RenderState(renderthread::RenderThread& thread) } RenderState::~RenderState() { - LOG_ALWAYS_FATAL_IF(mMeshState || mScissor || mStencil, + LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil, "State object lifecycle not managed correctly"); } void RenderState::onGLContextCreated() { - LOG_ALWAYS_FATAL_IF(mMeshState || mScissor || mStencil, + LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil, "State object lifecycle not managed correctly"); + mBlend = new Blend(); mMeshState = new MeshState(); mScissor = new Scissor(); mStencil = new Stencil(); // This is delayed because the first access of Caches makes GL calls - mCaches = &Caches::createInstance(*this); + if (!mCaches) { + mCaches = &Caches::createInstance(*this); + } mCaches->init(); mCaches->textureCache.setAssetAtlas(&mAssetAtlas); } @@ -92,6 +91,10 @@ void RenderState::onGLContextDestroyed() { std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext); mAssetAtlas.terminate(); + mCaches->terminate(); + + delete mBlend; + mBlend = nullptr; delete mMeshState; mMeshState = nullptr; delete mScissor; @@ -132,7 +135,7 @@ void RenderState::interruptForFunctorInvoke() { mCaches->currentProgram = nullptr; } } - mCaches->resetActiveTexture(); + mCaches->textureState().resetActiveTexture(); meshState().unbindMeshBuffer(); meshState().unbindIndicesBuffer(); meshState().resetVertexPointers(); @@ -148,14 +151,10 @@ void RenderState::resumeFromFunctorInvoke() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); scissor().invalidate(); + blend().invalidate(); - mCaches->activeTexture(0); - mCaches->resetBoundTextures(); - - mCaches->blend = true; - glEnable(GL_BLEND); - glBlendFunc(mCaches->lastSrcMode, mCaches->lastDstMode); - glBlendEquation(GL_FUNC_ADD); + mCaches->textureState().activateTexture(0); + mCaches->textureState().resetBoundTextures(); } void RenderState::debugOverdraw(bool enable, bool clear) { diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index d1ee64a..4180f44 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -24,7 +24,7 @@ #include <utils/RefBase.h> #include <private/hwui/DrawGlInfo.h> - +#include <renderstate/Blend.h> #include "AssetAtlas.h" #include "Caches.h" #include "renderstate/MeshState.h" @@ -84,6 +84,7 @@ public: void postDecStrong(VirtualLightRefBase* object); AssetAtlas& assetAtlas() { return mAssetAtlas; } + Blend& blend() { return *mBlend; } MeshState& meshState() { return *mMeshState; } Scissor& scissor() { return *mScissor; } Stencil& stencil() { return *mStencil; } @@ -100,11 +101,12 @@ private: renderthread::RenderThread& mRenderThread; - Caches* mCaches; + Caches* mCaches = nullptr; - MeshState* mMeshState; - Scissor* mScissor; - Stencil* mStencil; + Blend* mBlend = nullptr; + MeshState* mMeshState = nullptr; + Scissor* mScissor = nullptr; + Stencil* mStencil = nullptr; AssetAtlas mAssetAtlas; std::set<Layer*> mActiveLayers; diff --git a/libs/hwui/renderstate/TextureState.cpp b/libs/hwui/renderstate/TextureState.cpp new file mode 100644 index 0000000..1a638d2 --- /dev/null +++ b/libs/hwui/renderstate/TextureState.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <renderstate/TextureState.h> + +namespace android { +namespace uirenderer { + +// Must define as many texture units as specified by kTextureUnitsCount +const GLenum kTextureUnits[] = { + GL_TEXTURE0, + GL_TEXTURE1, + GL_TEXTURE2 +}; + +TextureState::TextureState() + : mTextureUnit(0) { + glActiveTexture(kTextureUnits[0]); + resetBoundTextures(); + + GLint maxTextureUnits; + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits); + LOG_ALWAYS_FATAL_IF(maxTextureUnits < kTextureUnitsCount, + "At least %d texture units are required!", kTextureUnitsCount); +} + +void TextureState::activateTexture(GLuint textureUnit) { + if (mTextureUnit != textureUnit) { + glActiveTexture(kTextureUnits[textureUnit]); + mTextureUnit = textureUnit; + } +} + +void TextureState::resetActiveTexture() { + mTextureUnit = -1; +} + +void TextureState::bindTexture(GLuint texture) { + if (mBoundTextures[mTextureUnit] != texture) { + glBindTexture(GL_TEXTURE_2D, texture); + mBoundTextures[mTextureUnit] = texture; + } +} + +void TextureState::bindTexture(GLenum target, GLuint texture) { + if (target == GL_TEXTURE_2D) { + bindTexture(texture); + } else { + // GLConsumer directly calls glBindTexture() with + // target=GL_TEXTURE_EXTERNAL_OES, don't cache this target + // since the cached state could be stale + glBindTexture(target, texture); + } +} + +void TextureState::deleteTexture(GLuint texture) { + // When glDeleteTextures() is called on a currently bound texture, + // OpenGL ES specifies that the texture is then considered unbound + // Consider the following series of calls: + // + // glGenTextures -> creates texture name 2 + // glBindTexture(2) + // glDeleteTextures(2) -> 2 is now unbound + // glGenTextures -> can return 2 again + // + // If we don't call glBindTexture(2) after the second glGenTextures + // call, any texture operation will be performed on the default + // texture (name=0) + + unbindTexture(texture); + + glDeleteTextures(1, &texture); +} + +void TextureState::resetBoundTextures() { + for (int i = 0; i < kTextureUnitsCount; i++) { + mBoundTextures[i] = 0; + } +} + +void TextureState::unbindTexture(GLuint texture) { + for (int i = 0; i < kTextureUnitsCount; i++) { + if (mBoundTextures[i] == texture) { + mBoundTextures[i] = 0; + } + } +} + +} /* namespace uirenderer */ +} /* namespace android */ + diff --git a/libs/hwui/renderstate/TextureState.h b/libs/hwui/renderstate/TextureState.h new file mode 100644 index 0000000..5a57b9f --- /dev/null +++ b/libs/hwui/renderstate/TextureState.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef RENDERSTATE_TEXTURESTATE_H +#define RENDERSTATE_TEXTURESTATE_H + +#include "Vertex.h" + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <SkXfermode.h> +#include <memory> + +namespace android { +namespace uirenderer { + +class TextureState { + friend class Caches; // TODO: move to RenderState +public: + /** + * Activate the specified texture unit. The texture unit must + * be specified using an integer number (0 for GL_TEXTURE0 etc.) + */ + void activateTexture(GLuint textureUnit); + + /** + * Invalidate the cached value of the active texture unit. + */ + void resetActiveTexture(); + + /** + * Binds the specified texture as a GL_TEXTURE_2D texture. + * All texture bindings must be performed with this method or + * bindTexture(GLenum, GLuint). + */ + void bindTexture(GLuint texture); + + /** + * Binds the specified texture with the specified render target. + * All texture bindings must be performed with this method or + * bindTexture(GLuint). + */ + void bindTexture(GLenum target, GLuint texture); + + /** + * Deletes the specified texture and clears it from the cache + * of bound textures. + * All textures must be deleted using this method. + */ + void deleteTexture(GLuint texture); + + /** + * Signals that the cache of bound textures should be cleared. + * Other users of the context may have altered which textures are bound. + */ + void resetBoundTextures(); + + /** + * Clear the cache of bound textures. + */ + void unbindTexture(GLuint texture); +private: + // total number of texture units available for use + static const int kTextureUnitsCount = 3; + + TextureState(); + GLuint mTextureUnit; + + // Caches texture bindings for the GL_TEXTURE_2D target + GLuint mBoundTextures[kTextureUnitsCount]; +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif // RENDERSTATE_BLEND_H diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 28aa938..3afca2f 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -217,9 +217,6 @@ void EglManager::destroy() { if (mEglDisplay == EGL_NO_DISPLAY) return; usePBufferSurface(); - if (Caches::hasInstance()) { - Caches::getInstance().terminate(); - } mRenderThread.renderState().onGLContextDestroyed(); eglDestroyContext(mEglDisplay, mEglContext); diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index fcf222b..bf3387b 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -170,6 +170,9 @@ public class Location implements Parcelable { * Converts a coordinate to a String representation. The outputType * may be one of FORMAT_DEGREES, FORMAT_MINUTES, or FORMAT_SECONDS. * The coordinate must be a valid double between -180.0 and 180.0. + * This conversion is performed in a method that is dependent on the + * default locale, and so is not guaranteed to round-trip with + * {@link #convert(String)}. * * @throws IllegalArgumentException if coordinate is less than * -180.0, greater than 180.0, or is not a number. @@ -217,7 +220,9 @@ public class Location implements Parcelable { /** * Converts a String in one of the formats described by * FORMAT_DEGREES, FORMAT_MINUTES, or FORMAT_SECONDS into a - * double. + * double. This conversion is performed in a locale agnostic + * method, and so is not guaranteed to round-trip with + * {@link #convert(double, int)}. * * @throws NullPointerException if coordinate is null * @throws IllegalArgumentException if the coordinate is not diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 9bcf3c8..f448dc2 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -346,6 +346,31 @@ public class AudioManager { */ public static final int ADJUST_SAME = 0; + /** + * Mute the volume. Has no effect if the stream is already muted. + * + * @see #adjustVolume(int, int) + * @see #adjustStreamVolume(int, int, int) + */ + public static final int ADJUST_MUTE = -100; + + /** + * Unmute the volume. Has no effect if the stream is not muted. + * + * @see #adjustVolume(int, int) + * @see #adjustStreamVolume(int, int, int) + */ + public static final int ADJUST_UNMUTE = 100; + + /** + * Toggle the mute state. If muted the stream will be unmuted. If not muted + * the stream will be muted. + * + * @see #adjustVolume(int, int) + * @see #adjustStreamVolume(int, int, int) + */ + public static final int ADJUST_TOGGLE_MUTE = 101; + // Flags should be powers of 2! /** @@ -777,13 +802,17 @@ public class AudioManager { * screen is showing. Another example, if music is playing in the background * and a call is not active, the music stream will be adjusted. * <p> - * This method should only be used by applications that replace the platform-wide - * management of audio settings or the main telephony application. - * <p>This method has no effect if the device implements a fixed volume policy + * This method should only be used by applications that replace the + * platform-wide management of audio settings or the main telephony + * application. + * <p> + * This method has no effect if the device implements a fixed volume policy * as indicated by {@link #isVolumeFixed()}. + * * @param direction The direction to adjust the volume. One of - * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or - * {@link #ADJUST_SAME}. + * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, + * {@link #ADJUST_SAME}, {@link #ADJUST_MUTE}, + * {@link #ADJUST_UNMUTE}, or {@link #ADJUST_TOGGLE_MUTE}. * @param flags One or more flags. * @see #adjustSuggestedStreamVolume(int, int, int) * @see #adjustStreamVolume(int, int, int) @@ -808,16 +837,20 @@ public class AudioManager { * Adjusts the volume of the most relevant stream, or the given fallback * stream. * <p> - * This method should only be used by applications that replace the platform-wide - * management of audio settings or the main telephony application. - * - * <p>This method has no effect if the device implements a fixed volume policy + * This method should only be used by applications that replace the + * platform-wide management of audio settings or the main telephony + * application. + * <p> + * This method has no effect if the device implements a fixed volume policy * as indicated by {@link #isVolumeFixed()}. + * * @param direction The direction to adjust the volume. One of - * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or - * {@link #ADJUST_SAME}. + * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, + * {@link #ADJUST_SAME}, {@link #ADJUST_MUTE}, + * {@link #ADJUST_UNMUTE}, or {@link #ADJUST_TOGGLE_MUTE}. * @param suggestedStreamType The stream type that will be used if there - * isn't a relevant stream. {@link #USE_DEFAULT_STREAM_TYPE} is valid here. + * isn't a relevant stream. {@link #USE_DEFAULT_STREAM_TYPE} is + * valid here. * @param flags One or more flags. * @see #adjustVolume(int, int) * @see #adjustStreamVolume(int, int, int) @@ -1088,72 +1121,72 @@ public class AudioManager { } /** - * Solo or unsolo a particular stream. All other streams are muted. - * <p> - * The solo command is protected against client process death: if a process - * with an active solo request on a stream dies, all streams that were muted - * because of this request will be unmuted automatically. - * <p> - * The solo requests for a given stream are cumulative: the AudioManager - * can receive several solo requests from one or more clients and the stream - * will be unsoloed only when the same number of unsolo requests are received. + * Solo or unsolo a particular stream. * <p> - * For a better user experience, applications MUST unsolo a soloed stream - * in onPause() and solo is again in onResume() if appropriate. - * <p>This method has no effect if the device implements a fixed volume policy - * as indicated by {@link #isVolumeFixed()}. + * Do not use. This method has been deprecated and is now a no-op. + * {@link #requestAudioFocus} should be used for exclusive audio playback. * * @param streamType The stream to be soloed/unsoloed. - * @param state The required solo state: true for solo ON, false for solo OFF - * + * @param state The required solo state: true for solo ON, false for solo + * OFF * @see #isVolumeFixed() + * @deprecated Do not use. If you need exclusive audio playback use + * {@link #requestAudioFocus}. */ + @Deprecated public void setStreamSolo(int streamType, boolean state) { - IAudioService service = getService(); - try { - service.setStreamSolo(streamType, state, mICallBack); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in setStreamSolo", e); - } + Log.w(TAG, "setStreamSolo has been deprecated. Do not use."); } /** * Mute or unmute an audio stream. * <p> - * The mute command is protected against client process death: if a process - * with an active mute request on a stream dies, this stream will be unmuted - * automatically. - * <p> - * The mute requests for a given stream are cumulative: the AudioManager - * can receive several mute requests from one or more clients and the stream - * will be unmuted only when the same number of unmute requests are received. - * <p> - * For a better user experience, applications MUST unmute a muted stream - * in onPause() and mute is again in onResume() if appropriate. + * This method should only be used by applications that replace the + * platform-wide management of audio settings or the main telephony + * application. * <p> - * This method should only be used by applications that replace the platform-wide - * management of audio settings or the main telephony application. - * <p>This method has no effect if the device implements a fixed volume policy + * This method has no effect if the device implements a fixed volume policy * as indicated by {@link #isVolumeFixed()}. + * <p> + * This method was deprecated in API level 22. Prior to API level 22 this + * method had significantly different behavior and should be used carefully. + * The following applies only to pre-22 platforms: + * <ul> + * <li>The mute command is protected against client process death: if a + * process with an active mute request on a stream dies, this stream will be + * unmuted automatically.</li> + * <li>The mute requests for a given stream are cumulative: the AudioManager + * can receive several mute requests from one or more clients and the stream + * will be unmuted only when the same number of unmute requests are + * received.</li> + * <li>For a better user experience, applications MUST unmute a muted stream + * in onPause() and mute is again in onResume() if appropriate.</li> + * </ul> * * @param streamType The stream to be muted/unmuted. - * @param state The required mute state: true for mute ON, false for mute OFF - * + * @param state The required mute state: true for mute ON, false for mute + * OFF * @see #isVolumeFixed() + * @deprecated Use {@link #adjustStreamVolume(int, int, int)} with + * {@link #ADJUST_MUTE} or {@link #ADJUST_UNMUTE} instead. */ + @Deprecated public void setStreamMute(int streamType, boolean state) { - IAudioService service = getService(); - try { - service.setStreamMute(streamType, state, mICallBack); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in setStreamMute", e); + Log.w(TAG, "setStreamMute is deprecated. adjustStreamVolume should be used instead."); + int direction = state ? ADJUST_MUTE : ADJUST_UNMUTE; + if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { + adjustSuggestedStreamVolume(direction, streamType, 0); + } else { + adjustStreamVolume(streamType, direction, 0); } } /** - * get stream mute state. + * Returns the current mute state for a particular stream. * - * @hide + * @param streamType The stream to get mute state for. + * @return The mute state for the given stream. + * @see #adjustStreamVolume(int, int, int) */ public boolean isStreamMute(int streamType) { IAudioService service = getService(); @@ -1166,29 +1199,6 @@ public class AudioManager { } /** - * set master mute state. - * - * @hide - */ - public void setMasterMute(boolean state) { - setMasterMute(state, FLAG_SHOW_UI); - } - - /** - * set master mute state with optional flags. - * - * @hide - */ - public void setMasterMute(boolean state, int flags) { - IAudioService service = getService(); - try { - service.setMasterMute(state, flags, mContext.getOpPackageName(), mICallBack); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in setMasterMute", e); - } - } - - /** * get master mute state. * * @hide diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java index 616bdd1..873c142 100644 --- a/media/java/android/media/AudioManagerInternal.java +++ b/media/java/android/media/AudioManagerInternal.java @@ -41,9 +41,6 @@ public abstract class AudioManagerInternal { public abstract void adjustMasterVolumeForUid(int steps, int flags, String callingPackage, int uid); - public abstract void setMasterMuteForUid(boolean state, int flags, String callingPackage, - IBinder cb, int uid); - public abstract void setRingerModeDelegate(RingerModeDelegate delegate); public abstract int getRingerModeInternal(); diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 0ffa5fc..d96fee6 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -748,7 +748,7 @@ public class AudioService extends IAudioService.Stub { setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]]); } // apply stream volume - if (!mStreamStates[streamType].isMuted_syncVSS()) { + if (!mStreamStates[streamType].mIsMuted) { mStreamStates[streamType].applyAllVolumes(); } } @@ -970,6 +970,7 @@ public class AudioService extends IAudioService.Stub { if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream="+suggestedStreamType + ", flags=" + flags); int streamType; + boolean isMute = isMuteAdjust(direction); if (mVolumeControlStream != -1) { streamType = mVolumeControlStream; } else { @@ -984,7 +985,8 @@ public class AudioService extends IAudioService.Stub { } // For notifications/ring, show the ui before making any adjustments - if (mVolumeController.suppressAdjustment(resolvedStream, flags)) { + // Don't suppress mute/unmute requests + if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) { direction = 0; flags &= ~AudioManager.FLAG_PLAY_SOUND; flags &= ~AudioManager.FLAG_VIBRATE; @@ -1011,10 +1013,17 @@ public class AudioService extends IAudioService.Stub { ensureValidDirection(direction); ensureValidStreamType(streamType); + boolean isMuteAdjust = isMuteAdjust(direction); + // use stream type alias here so that streams with same alias have the same behavior, // including with regard to silent mode control (e.g the use of STREAM_RING below and in // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION) int streamTypeAlias = mStreamVolumeAlias[streamType]; + + if (isMuteAdjust && !isStreamAffectedByMute(streamTypeAlias)) { + return; + } + VolumeStreamState streamState = mStreamStates[streamTypeAlias]; final int device = getDeviceForStream(streamTypeAlias); @@ -1100,13 +1109,37 @@ public class AudioService extends IAudioService.Stub { } } - if ((direction == AudioManager.ADJUST_RAISE) && + if (isMuteAdjust) { + boolean state; + if (direction == AudioManager.ADJUST_TOGGLE_MUTE) { + state = !streamState.mIsMuted; + } else { + state = direction == AudioManager.ADJUST_MUTE; + } + if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { + setSystemAudioMute(state); + } + for (int stream = 0; stream < mStreamStates.length; stream++) { + if (streamTypeAlias == mStreamVolumeAlias[stream]) { + mStreamStates[stream].mute(state); + + Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, stream); + intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state); + sendBroadcastToAll(intent); + } + } + } else if ((direction == AudioManager.ADJUST_RAISE) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { - Log.e(TAG, "adjustStreamVolume() safe volume index = "+oldIndex); + Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex); mVolumeController.postDisplaySafeVolumeWarning(flags); - } else if (streamState.adjustIndex(direction * step, device)) { - // Post message to set system volume (it in turn will post a message - // to persist). Do not change volume if stream is muted. + } else if (streamState.adjustIndex(direction * step, device) || streamState.mIsMuted) { + // Post message to set system volume (it in turn will post a + // message to persist). + if (streamState.mIsMuted) { + // Unmute the stream if it was previously muted + streamState.mute(false); + } sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, @@ -1116,7 +1149,7 @@ public class AudioService extends IAudioService.Stub { 0); } - // Check if volume update should be send to Hdmi system audio. + // Check if volume update should be sent to Hdmi system audio. int newIndex = mStreamStates[streamType].getIndex(device); if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags); @@ -1129,7 +1162,7 @@ public class AudioService extends IAudioService.Stub { oldIndex != newIndex) { synchronized (mHdmiPlaybackClient) { int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN : - KeyEvent.KEYCODE_VOLUME_UP; + KeyEvent.KEYCODE_VOLUME_UP; mHdmiPlaybackClient.sendKeyEvent(keyCode, true); mHdmiPlaybackClient.sendKeyEvent(keyCode, false); } @@ -1172,6 +1205,10 @@ public class AudioService extends IAudioService.Stub { if (mUseFixedVolume) { return; } + if (isMuteAdjust(steps)) { + setMasterMuteInternal(steps, flags, callingPackage, uid); + return; + } ensureValidSteps(steps); int volume = Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME); int delta = 0; @@ -1500,46 +1537,6 @@ public class AudioService extends IAudioService.Stub { } } - /** @see AudioManager#setStreamSolo(int, boolean) */ - public void setStreamSolo(int streamType, boolean state, IBinder cb) { - if (mUseFixedVolume) { - return; - } - int streamAlias = mStreamVolumeAlias[streamType]; - for (int stream = 0; stream < mStreamStates.length; stream++) { - if (!isStreamAffectedByMute(streamAlias) || streamAlias == mStreamVolumeAlias[stream]) { - continue; - } - mStreamStates[stream].mute(cb, state); - } - } - - /** @see AudioManager#setStreamMute(int, boolean) */ - public void setStreamMute(int streamType, boolean state, IBinder cb) { - if (mUseFixedVolume) { - return; - } - if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { - streamType = getActiveStreamType(streamType); - } - int streamAlias = mStreamVolumeAlias[streamType]; - if (isStreamAffectedByMute(streamAlias)) { - if (streamAlias == AudioSystem.STREAM_MUSIC) { - setSystemAudioMute(state); - } - for (int stream = 0; stream < mStreamStates.length; stream++) { - if (streamAlias == mStreamVolumeAlias[stream]) { - mStreamStates[stream].mute(cb, state); - - Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); - intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, stream); - intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state); - sendBroadcastToAll(intent); - } - } - } - } - private void setSystemAudioMute(boolean state) { if (mHdmiManager == null || mHdmiTvClient == null) return; synchronized (mHdmiManager) { @@ -1561,7 +1558,7 @@ public class AudioService extends IAudioService.Stub { streamType = getActiveStreamType(streamType); } synchronized (VolumeStreamState.class) { - return mStreamStates[streamType].isMuted_syncVSS(); + return mStreamStates[streamType].mIsMuted; } } @@ -1665,20 +1662,17 @@ public class AudioService extends IAudioService.Stub { } } - /** @see AudioManager#setMasterMute(boolean, int) */ - public void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb) { - setMasterMuteInternal(state, flags, callingPackage, cb, Binder.getCallingUid()); - } - - private void setMasterMuteInternal(boolean state, int flags, String callingPackage, IBinder cb, - int uid) { - if (mUseFixedVolume) { - return; - } + private void setMasterMuteInternal(int adjust, int flags, String callingPackage, int uid) { if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } + boolean state; + if (adjust == AudioManager.ADJUST_TOGGLE_MUTE) { + state = !AudioSystem.getMasterMute(); + } else { + state = adjust == AudioManager.ADJUST_MUTE; + } if (state != AudioSystem.getMasterMute()) { setSystemAudioMute(state); AudioSystem.setMasterMute(state); @@ -1714,7 +1708,7 @@ public class AudioService extends IAudioService.Stub { int index = mStreamStates[streamType].getIndex(device); // by convention getStreamVolume() returns 0 when a stream is muted. - if (mStreamStates[streamType].isMuted_syncVSS()) { + if (mStreamStates[streamType].mIsMuted) { index = 0; } if (index != 0 && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && @@ -1934,11 +1928,11 @@ public class AudioService extends IAudioService.Stub { } } } - mStreamStates[streamType].mute(null, false); + mStreamStates[streamType].mute(false); mRingerModeMutedStreams &= ~(1 << streamType); } else { // mute - mStreamStates[streamType].mute(null, true); + mStreamStates[streamType].mute(true); mRingerModeMutedStreams |= (1 << streamType); } } @@ -2426,13 +2420,9 @@ public class AudioService extends IAudioService.Stub { streamState.readSettings(); synchronized (VolumeStreamState.class) { // unmute stream that was muted but is not affect by mute anymore - if (streamState.isMuted_syncVSS() && ((!isStreamAffectedByMute(streamType) && + if (streamState.mIsMuted && ((!isStreamAffectedByMute(streamType) && !isStreamMutedByRingerMode(streamType)) || mUseFixedVolume)) { - int size = streamState.mDeathHandlers.size(); - for (int i = 0; i < size; i++) { - streamState.mDeathHandlers.get(i).mMuteCount = 1; - streamState.mDeathHandlers.get(i).mute_syncVSS(false); - } + streamState.mIsMuted = false; } } } @@ -3221,8 +3211,16 @@ public class AudioService extends IAudioService.Stub { } private void ensureValidDirection(int direction) { - if (direction < AudioManager.ADJUST_LOWER || direction > AudioManager.ADJUST_RAISE) { - throw new IllegalArgumentException("Bad direction " + direction); + switch (direction) { + case AudioManager.ADJUST_LOWER: + case AudioManager.ADJUST_RAISE: + case AudioManager.ADJUST_SAME: + case AudioManager.ADJUST_MUTE: + case AudioManager.ADJUST_UNMUTE: + case AudioManager.ADJUST_TOGGLE_MUTE: + break; + default: + throw new IllegalArgumentException("Bad direction " + direction); } } @@ -3238,6 +3236,11 @@ public class AudioService extends IAudioService.Stub { } } + private boolean isMuteAdjust(int adjust) { + return adjust == AudioManager.ADJUST_MUTE || adjust == AudioManager.ADJUST_UNMUTE + || adjust == AudioManager.ADJUST_TOGGLE_MUTE; + } + private boolean isInCommunication() { boolean IsInCall = false; @@ -3467,11 +3470,11 @@ public class AudioService extends IAudioService.Stub { public class VolumeStreamState { private final int mStreamType; + private boolean mIsMuted; private String mVolumeIndexSettingName; private int mIndexMax; private final ConcurrentHashMap<Integer, Integer> mIndex = new ConcurrentHashMap<Integer, Integer>(8, 0.75f, 4); - private ArrayList<VolumeDeathHandler> mDeathHandlers; //handles mute/solo clients death private VolumeStreamState(String settingName, int streamType) { @@ -3482,9 +3485,6 @@ public class AudioService extends IAudioService.Stub { AudioSystem.initStreamVolume(streamType, 0, mIndexMax); mIndexMax *= 10; - // mDeathHandlers must be created before calling readSettings() - mDeathHandlers = new ArrayList<VolumeDeathHandler>(); - readSettings(); } @@ -3549,7 +3549,7 @@ public class AudioService extends IAudioService.Stub { // must be called while synchronized VolumeStreamState.class public void applyDeviceVolume_syncVSS(int device) { int index; - if (isMuted_syncVSS()) { + if (mIsMuted) { index = 0; } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) || ((device & mFullVolumeDevices) != 0)) { @@ -3565,7 +3565,7 @@ public class AudioService extends IAudioService.Stub { // apply default volume first: by convention this will reset all // devices volumes in audio policy manager to the supplied value int index; - if (isMuted_syncVSS()) { + if (mIsMuted) { index = 0; } else { index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; @@ -3578,7 +3578,7 @@ public class AudioService extends IAudioService.Stub { Map.Entry entry = (Map.Entry)i.next(); int device = ((Integer)entry.getKey()).intValue(); if (device != AudioSystem.DEVICE_OUT_DEFAULT) { - if (isMuted_syncVSS()) { + if (mIsMuted) { index = 0; } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) @@ -3688,14 +3688,20 @@ public class AudioService extends IAudioService.Stub { } } - public void mute(IBinder cb, boolean state) { + public void mute(boolean state) { synchronized (VolumeStreamState.class) { - VolumeDeathHandler handler = getDeathHandler_syncVSS(cb, state); - if (handler == null) { - Log.e(TAG, "Could not get client death handler for stream: "+mStreamType); - return; + if (state != mIsMuted) { + mIsMuted = state; + // Set the new mute volume. This propagates the values to + // the audio system, otherwise the volume won't be changed + // at the lower level. + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + this, 0); } - handler.mute_syncVSS(state); } } @@ -3733,117 +3739,9 @@ public class AudioService extends IAudioService.Stub { return index; } - private class VolumeDeathHandler implements IBinder.DeathRecipient { - private IBinder mICallback; // To be notified of client's death - private int mMuteCount; // Number of active mutes for this client - - VolumeDeathHandler(IBinder cb) { - mICallback = cb; - } - - // must be called while synchronized VolumeStreamState.class - public void mute_syncVSS(boolean state) { - boolean updateVolume = false; - if (state) { - if (mMuteCount == 0) { - // Register for client death notification - try { - // mICallback can be 0 if muted by AudioService - if (mICallback != null) { - mICallback.linkToDeath(this, 0); - } - VolumeStreamState.this.mDeathHandlers.add(this); - // If the stream is not yet muted by any client, set level to 0 - if (!VolumeStreamState.this.isMuted_syncVSS()) { - updateVolume = true; - } - } catch (RemoteException e) { - // Client has died! - binderDied(); - return; - } - } else { - Log.w(TAG, "stream: "+mStreamType+" was already muted by this client"); - } - mMuteCount++; - } else { - if (mMuteCount == 0) { - Log.e(TAG, "unexpected unmute for stream: "+mStreamType); - } else { - mMuteCount--; - if (mMuteCount == 0) { - // Unregister from client death notification - VolumeStreamState.this.mDeathHandlers.remove(this); - // mICallback can be 0 if muted by AudioService - if (mICallback != null) { - mICallback.unlinkToDeath(this, 0); - } - if (!VolumeStreamState.this.isMuted_syncVSS()) { - updateVolume = true; - } - } - } - } - if (updateVolume) { - sendMsg(mAudioHandler, - MSG_SET_ALL_VOLUMES, - SENDMSG_QUEUE, - 0, - 0, - VolumeStreamState.this, 0); - } - } - - public void binderDied() { - Log.w(TAG, "Volume service client died for stream: "+mStreamType); - synchronized (VolumeStreamState.class) { - if (mMuteCount != 0) { - // Reset all active mute requests from this client. - mMuteCount = 1; - mute_syncVSS(false); - } - } - } - } - - private int muteCount() { - int count = 0; - int size = mDeathHandlers.size(); - for (int i = 0; i < size; i++) { - count += mDeathHandlers.get(i).mMuteCount; - } - return count; - } - - // must be called while synchronized VolumeStreamState.class - private boolean isMuted_syncVSS() { - return muteCount() != 0; - } - - // must be called while synchronized VolumeStreamState.class - private VolumeDeathHandler getDeathHandler_syncVSS(IBinder cb, boolean state) { - VolumeDeathHandler handler; - int size = mDeathHandlers.size(); - for (int i = 0; i < size; i++) { - handler = mDeathHandlers.get(i); - if (cb == handler.mICallback) { - return handler; - } - } - // If this is the first mute request for this client, create a new - // client death handler. Otherwise, it is an out of sequence unmute request. - if (state) { - handler = new VolumeDeathHandler(cb); - } else { - Log.w(TAG, "stream was not muted by this client"); - handler = null; - } - return handler; - } - private void dump(PrintWriter pw) { - pw.print(" Mute count: "); - pw.println(muteCount()); + pw.print(" Muted: "); + pw.println(mIsMuted); pw.print(" Max: "); pw.println((mIndexMax + 5) / 10); pw.print(" Current: "); @@ -5648,7 +5546,10 @@ public class AudioService extends IAudioService.Stub { Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT); } - public boolean suppressAdjustment(int resolvedStream, int flags) { + public boolean suppressAdjustment(int resolvedStream, int flags, boolean isMute) { + if (isMute) { + return false; + } boolean suppress = false; if (resolvedStream == AudioSystem.STREAM_RING && mController != null) { final long now = SystemClock.uptimeMillis(); @@ -5801,12 +5702,6 @@ public class AudioService extends IAudioService.Stub { public void setRingerModeInternal(int ringerMode, String caller) { AudioService.this.setRingerModeInternal(ringerMode, caller); } - - @Override - public void setMasterMuteForUid(boolean state, int flags, String callingPackage, IBinder cb, - int uid) { - setMasterMuteInternal(state, flags, callingPackage, cb, uid); - } } //========================================================================================== diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index fad3cec..bfb78a1 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -52,16 +52,10 @@ interface IAudioService { void setMasterVolume(int index, int flags, String callingPackage); - void setStreamSolo(int streamType, boolean state, IBinder cb); - - void setStreamMute(int streamType, boolean state, IBinder cb); - boolean isStreamMute(int streamType); void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb); - void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb); - boolean isMasterMute(); int getStreamVolume(int streamType); diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index 7ea269b..9954de5 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -222,13 +222,9 @@ public class MediaSessionLegacyHelper { direction, flags); } else if (isMute) { if (down) { - // We need to send two volume events on down, one to mute - // and one to show the UI mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, - MediaSessionManager.DIRECTION_MUTE, flags); + AudioManager.ADJUST_TOGGLE_MUTE, flags); } - mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, - 0 /* direction, causes UI to show on down */, flags); } } } diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index a4ef851..b4fff8f 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -59,14 +59,6 @@ public final class MediaSessionManager { private Context mContext; /** - * Special flag for sending the mute key to dispatchAdjustVolume used by the - * system. - * - * @hide - */ - public static final int DIRECTION_MUTE = -99; - - /** * @hide */ public MediaSessionManager(Context context) { diff --git a/packages/SystemUI/res/layout/recents.xml b/packages/SystemUI/res/layout/recents.xml index 8f367a6..26523f9 100644 --- a/packages/SystemUI/res/layout/recents.xml +++ b/packages/SystemUI/res/layout/recents.xml @@ -15,7 +15,7 @@ --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" + android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Status Bar Scrim View --> <ImageView @@ -29,9 +29,16 @@ <!-- Recents View --> <com.android.systemui.recents.views.RecentsView android:id="@+id/recents_view" - android:layout_width="match_parent" + android:layout_width="match_parent" android:layout_height="match_parent" - android:focusable="true" /> + android:focusable="true"> + <!-- MultiStack Debug View --> + <ViewStub android:id="@+id/multistack_debug_view_stub" + android:layout="@layout/recents_multistack_debug" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left|bottom" /> + </com.android.systemui.recents.views.RecentsView> <!-- Empty View --> <ViewStub android:id="@+id/empty_view_stub" diff --git a/packages/SystemUI/res/layout/recents_multistack_debug.xml b/packages/SystemUI/res/layout/recents_multistack_debug.xml new file mode 100644 index 0000000..6524a54 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_multistack_debug.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="left|bottom" + android:orientation="vertical"> + <Button + android:id="@+id/add_stack" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:padding="8dp" + android:textSize="20sp" + android:textColor="#ffffffff" + android:text="@string/recents_multistack_add_stack" + android:fontFamily="sans-serif" + android:background="#000000" + android:alpha="0.5" /> + <Button + android:id="@+id/resize_stack" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:padding="8dp" + android:textSize="20sp" + android:textColor="#ffffffff" + android:text="@string/recents_multistack_resize_stack" + android:fontFamily="sans-serif" + android:background="#000000" + android:alpha="0.5" /> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_multistack_stack_size_dialog.xml b/packages/SystemUI/res/layout/recents_multistack_stack_size_dialog.xml new file mode 100644 index 0000000..36e54a0 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_multistack_stack_size_dialog.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="16dp" + android:orientation="vertical" + android:descendantFocusability="beforeDescendants" + android:focusableInTouchMode="true"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <EditText + android:id="@+id/inset_left" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="Left" + android:singleLine="true" + android:imeOptions="actionNext" + android:inputType="number" + android:selectAllOnFocus="true" /> + <EditText + android:id="@+id/inset_top" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="Top" + android:singleLine="true" + android:imeOptions="actionNext" + android:inputType="number" + android:selectAllOnFocus="true" /> + <EditText + android:id="@+id/inset_right" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="Right" + android:singleLine="true" + android:imeOptions="actionNext" + android:inputType="number" + android:selectAllOnFocus="true" /> + <EditText + android:id="@+id/inset_bottom" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:hint="Bottom" + android:singleLine="true" + android:imeOptions="actionDone" + android:inputType="number" + android:selectAllOnFocus="true" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml index f1d8ad0..53047a3 100644 --- a/packages/SystemUI/res/layout/recents_task_view_header.xml +++ b/packages/SystemUI/res/layout/recents_task_view_header.xml @@ -43,6 +43,16 @@ android:ellipsize="marquee" android:fadingEdge="horizontal" /> <com.android.systemui.recents.views.FixedSizeImageView + android:id="@+id/move_task" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginEnd="52dp" + android:layout_gravity="center_vertical|end" + android:padding="12dp" + android:background="@drawable/recents_button_bg" + android:src="@drawable/star" + android:visibility="gone" /> + <com.android.systemui.recents.views.FixedSizeImageView android:id="@+id/dismiss_task" android:layout_width="48dp" android:layout_height="48dp" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c977db9..40bf13f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -672,6 +672,19 @@ <!-- Recents: Dismiss all button. [CHAR LIMIT=NONE] --> <string name="recents_dismiss_all_message">Dismiss all applications</string> + <!-- Recents: MultiStack add stack button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_add_stack">+</string> + <!-- Recents: MultiStack remove stack button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_remove_stack">-</string> + <!-- Recents: MultiStack resize stack button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_resize_stack">[]</string> + <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string> + <!-- Recents: MultiStack add stack split vertical radio button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_add_stack_dialog_split_vertical">Split Vertical</string> + <!-- Recents: MultiStack add stack split custom radio button. [CHAR LIMIT=NONE] --> + <string name="recents_multistack_add_stack_dialog_split_custom">Split Custom</string> + <!-- Expanded Status Bar Header: Battery Charged [CHAR LIMIT=40] --> <string name="expanded_header_battery_charged">Charged</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index f2b4a69..bf19b8d 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -20,7 +20,7 @@ <item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item> </style> - <style name="RecentsTheme" parent="@android:style/Theme"> + <style name="RecentsTheme" parent="@android:style/Theme.Material.Light"> <!-- NoTitle --> <item name="android:windowNoTitle">true</item> <!-- Misc --> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 974235e..4dacacf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -423,7 +423,9 @@ public class QSPanel extends ViewGroup { } for (TileRecord record : mRecords) { - record.tileView.setDual(record.tile.supportsDualTargets()); + if (record.tileView.setDual(record.tile.supportsDualTargets())) { + record.tileView.handleStateChanged(record.tile.getState()); + } if (record.tileView.getVisibility() == GONE) continue; final int cw = record.row == 0 ? mLargeCellWidth : mCellWidth; final int ch = record.row == 0 ? mLargeCellHeight : mCellHeight; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index bb353d5..16ae6b4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -172,7 +172,7 @@ public class QSTileView extends ViewGroup { } } - public void setDual(boolean dual) { + public boolean setDual(boolean dual) { final boolean changed = dual != mDual; mDual = dual; if (changed) { @@ -199,6 +199,7 @@ public class QSTileView extends ViewGroup { setFocusable(!dual); mDivider.setVisibility(dual ? VISIBLE : GONE); postInvalidate(); + return changed; } private void setRipple(RippleDrawable tileBackground) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index cfd6b40..192acc6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -39,6 +39,8 @@ public class Constants { public static final boolean EnableSearchLayout = true; // Enables the thumbnail alpha on the front-most task public static final boolean EnableThumbnailAlphaOnFrontmost = false; + // Enables all system stacks to show up in the same recents stack + public static final boolean EnableMultiStackToSingleStack = true; // This disables the bitmap and icon caches public static final boolean DisableBackgroundCache = false; // Enables the simulated task affiliations diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 3c75aac..9dd82fc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -24,7 +24,6 @@ import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -56,7 +55,6 @@ import com.android.systemui.recents.views.TaskViewHeader; import com.android.systemui.recents.views.TaskViewTransform; import java.util.ArrayList; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -112,6 +110,9 @@ public class Recents extends SystemUI /** Preloads the next task */ public void run() { + // Temporarily skip this if multi stack is enabled + if (mConfig.multiStackEnabled) return; + RecentsConfiguration config = RecentsConfiguration.getInstance(); if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) { RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); @@ -362,13 +363,21 @@ public class Recents extends SystemUI } void showRelativeAffiliatedTask(boolean showNextTask) { + // Return early if there is no focused stack + int focusedStackId = mSystemServicesProxy.getFocusedStack(); + TaskStack focusedStack = null; RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); loader.preloadTasks(plan, true /* isTopTaskHome */); - TaskStack stack = plan.getTaskStack(); + if (mConfig.multiStackEnabled) { + if (focusedStackId < 0) return; + focusedStack = plan.getTaskStack(focusedStackId); + } else { + focusedStack = plan.getAllTaskStacks().get(0); + } - // Return early if there are no tasks - if (stack.getTaskCount() == 0) return; + // Return early if there are no tasks in the focused stack + if (focusedStack.getTaskCount() == 0) return; ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask(); // Return early if there is no running task (can't determine affiliated tasks in this case) @@ -377,7 +386,7 @@ public class Recents extends SystemUI if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return; // Find the task in the recents list - ArrayList<Task> tasks = stack.getTasks(); + ArrayList<Task> tasks = focusedStack.getTasks(); Task toTask = null; ActivityOptions launchOpts = null; int taskCount = tasks.size(); @@ -399,7 +408,7 @@ public class Recents extends SystemUI R.anim.recents_launch_prev_affiliated_task_source); } if (toTaskKey != null) { - toTask = stack.findTaskWithId(toTaskKey.id); + toTask = focusedStack.findTaskWithId(toTaskKey.id); } numAffiliatedTasks = group.getTaskCount(); break; @@ -473,8 +482,9 @@ public class Recents extends SystemUI // Reload the widget id before we get the task stack bounds reloadSearchBarAppWidget(mContext, mSystemServicesProxy); } - mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight, - (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), mTaskStackBounds); + mConfig.getAvailableTaskStackBounds(mWindowRect.width(), mWindowRect.height(), + mStatusBarHeight, (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), + mTaskStackBounds); if (mConfig.isLandscape && mConfig.hasTransposedNavBar) { mSystemInsets.set(0, mStatusBarHeight, mNavBarWidth, 0); } else { @@ -653,8 +663,25 @@ public class Recents extends SystemUI // Create a new load plan if onPreloadRecents() was never triggered sInstanceLoadPlan = loader.createLoadPlan(mContext); } + + // Temporarily skip the transition (use a dummy fade) if multi stack is enabled. + // For multi-stack we need to figure out where each of the tasks are going. + if (mConfig.multiStackEnabled) { + loader.preloadTasks(sInstanceLoadPlan, true); + ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks(); + TaskStack stack = stacks.get(0); + mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, true); + TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = + mDummyStackView.computeStackVisibilityReport(); + ActivityOptions opts = getUnknownTransitionActivityOptions(); + startAlternateRecentsActivity(topTask, opts, true /* fromHome */, + false /* fromSearchHome */, false /* fromThumbnail */, stackVr); + return; + } + loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); - TaskStack stack = sInstanceLoadPlan.getTaskStack(); + ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks(); + TaskStack stack = stacks.get(0); // Prepare the dummy stack for the transition mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 1833e09..b1ac733 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -18,6 +18,7 @@ package com.android.systemui.recents; import android.app.Activity; import android.app.ActivityOptions; +import android.app.Dialog; import android.app.SearchManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; @@ -43,7 +44,6 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.SpaceNode; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.DebugOverlayView; @@ -75,6 +75,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView View mEmptyView; DebugOverlayView mDebugOverlay; + // MultiStack debug + RecentsMultiStackDialog mMultiStackDebugDialog; + // Search AppWidget RecentsAppWidgetHost mAppWidgetHost; AppWidgetProviderInfo mSearchAppWidgetInfo; @@ -190,7 +193,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } // Start loading tasks according to the load plan - if (plan.getTaskStack() == null) { + ArrayList<TaskStack> stacks = plan.getAllTaskStacks(); + if (stacks.size() == 0) { loader.preloadTasks(plan, mConfig.launchedFromHome); } RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); @@ -199,9 +203,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView loadOpts.numVisibleTaskThumbnails = mConfig.launchedNumVisibleThumbnails; loader.loadTasks(this, plan, loadOpts); - SpaceNode root = plan.getSpaceNode(); - ArrayList<TaskStack> stacks = root.getStacks(); - boolean hasTasks = root.hasTasks(); + boolean hasTasks = plan.hasTasks(); if (hasTasks) { mRecentsView.setTaskStacks(stacks); } @@ -591,6 +593,40 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } } + + /**** RecentsMultiStackDialog ****/ + + private RecentsMultiStackDialog getMultiStackDebugDialog() { + if (mMultiStackDebugDialog == null) { + mMultiStackDebugDialog = new RecentsMultiStackDialog(getFragmentManager()); + } + return mMultiStackDebugDialog; + } + + @Override + public void onMultiStackAddStack() { + RecentsMultiStackDialog dialog = getMultiStackDebugDialog(); + dialog.showAddStackDialog(); + } + + @Override + public void onMultiStackResizeStack() { + RecentsMultiStackDialog dialog = getMultiStackDebugDialog(); + dialog.showResizeStackDialog(); + } + + @Override + public void onMultiStackRemoveStack() { + RecentsMultiStackDialog dialog = getMultiStackDebugDialog(); + dialog.showRemoveStackDialog(); + } + + @Override + public void onMultiStackMoveTask(Task t) { + RecentsMultiStackDialog dialog = getMultiStackDebugDialog(); + dialog.showMoveTaskDialog(t); + } + /**** RecentsView.RecentsViewCallbacks Implementation ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index bc10a48..1736c77 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -134,6 +134,7 @@ public class RecentsConfiguration { public boolean fakeShadows; /** Dev options and global settings */ + public boolean multiStackEnabled; public boolean lockToAppEnabled; public boolean developerOptionsEnabled; public boolean debugModeEnabled; @@ -294,6 +295,7 @@ public class RecentsConfiguration { Settings.Global.DEVELOPMENT_SETTINGS_ENABLED) != 0; lockToAppEnabled = ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0; + multiStackEnabled = "1".equals(ssp.getSystemProperty("overview.enableMultiStack")); } /** Called when the configuration has changed, and we want to reset any configuration specific @@ -335,8 +337,8 @@ public class RecentsConfiguration { * Returns the task stack bounds in the current orientation. These bounds do not account for * the system insets. */ - public void getTaskStackBounds(int windowWidth, int windowHeight, int topInset, int rightInset, - Rect taskStackBounds) { + public void getAvailableTaskStackBounds(int windowWidth, int windowHeight, int topInset, + int rightInset, Rect taskStackBounds) { Rect searchBarBounds = new Rect(); getSearchBarBounds(windowWidth, windowHeight, topInset, searchBarBounds); if (isLandscape && hasTransposedSearchBar) { @@ -353,7 +355,7 @@ public class RecentsConfiguration { * the system insets. */ public void getSearchBarBounds(int windowWidth, int windowHeight, int topInset, - Rect searchBarSpaceBounds) { + Rect searchBarSpaceBounds) { // Return empty rects if search is not enabled int searchBarSize = searchBarSpaceHeightPx; if (!Constants.DebugFlags.App.EnableSearchLayout || !hasSearchBarAppWidget()) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsMultiStackDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsMultiStackDialog.java new file mode 100644 index 0000000..fdf9d39 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsMultiStackDialog.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents; + +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; +import android.graphics.Rect; +import android.os.Bundle; +import android.util.MutableInt; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; +import com.android.systemui.R; +import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.RecentsTaskLoader; +import com.android.systemui.recents.model.Task; + +import java.util.List; + +/** + * A helper for the dialogs that show when multistack debugging is on. + */ +public class RecentsMultiStackDialog extends DialogFragment { + + static final String TAG = "RecentsMultiStackDialog"; + + public static final int ADD_STACK_DIALOG = 0; + public static final int ADD_STACK_PICK_APP_DIALOG = 1; + public static final int REMOVE_STACK_DIALOG = 2; + public static final int RESIZE_STACK_DIALOG = 3; + public static final int RESIZE_STACK_PICK_STACK_DIALOG = 4; + public static final int MOVE_TASK_DIALOG = 5; + + FragmentManager mFragmentManager; + int mCurrentDialogType; + MutableInt mTargetStackIndex = new MutableInt(0); + Task mTaskToMove; + SparseArray<ActivityManager.StackInfo> mStacks; + List<ResolveInfo> mLauncherActivities; + Rect mAddStackRect; + Intent mAddStackIntent; + + View mAddStackDialogContent; + + public RecentsMultiStackDialog() {} + + public RecentsMultiStackDialog(FragmentManager mgr) { + mFragmentManager = mgr; + } + + /** Shows the add-stack dialog. */ + void showAddStackDialog() { + mCurrentDialogType = ADD_STACK_DIALOG; + show(mFragmentManager, TAG); + } + + /** Creates a new add-stack dialog. */ + private void createAddStackDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + builder.setTitle("Add Stack - Enter new dimensions"); + mAddStackDialogContent = + inflater.inflate(R.layout.recents_multistack_stack_size_dialog, null, false); + Rect windowRect = ssp.getWindowRect(); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_left, windowRect.left); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_top, windowRect.top); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_right, windowRect.right); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_bottom, windowRect.bottom); + builder.setView(mAddStackDialogContent); + builder.setPositiveButton("Add Stack", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int left = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_left); + int top = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_top); + int right = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_right); + int bottom = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_bottom); + if (bottom <= top || right <= left) { + Toast.makeText(context, "Invalid dimensions", Toast.LENGTH_SHORT).show(); + dismiss(); + return; + } + + // Prompt the user for the app to start + dismiss(); + mCurrentDialogType = ADD_STACK_PICK_APP_DIALOG; + mAddStackRect = new Rect(left, top, right, bottom); + show(mFragmentManager, TAG); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + } + + /** Creates a new add-stack pick-app dialog. */ + private void createAddStackPickAppDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + mLauncherActivities = ssp.getLauncherApps(); + mAddStackIntent = null; + int activityCount = mLauncherActivities.size(); + CharSequence[] activityNames = new CharSequence[activityCount]; + for (int i = 0; i < activityCount; i++) { + activityNames[i] = ssp.getActivityLabel(mLauncherActivities.get(i).activityInfo); + } + builder.setTitle("Add Stack - Pick starting app"); + builder.setSingleChoiceItems(activityNames, -1, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ActivityInfo ai = mLauncherActivities.get(which).activityInfo; + mAddStackIntent = new Intent(Intent.ACTION_MAIN); + mAddStackIntent.addCategory(Intent.CATEGORY_LAUNCHER); + mAddStackIntent.setComponent(new ComponentName(ai.packageName, ai.name)); + } + }); + builder.setPositiveButton("Add Stack", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Display 0 = default display + ssp.createNewStack(0, mAddStackRect, mAddStackIntent); + } + }); + builder.setNegativeButton("Skip", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Display 0 = default display + ssp.createNewStack(0, mAddStackRect, null); + } + }); + } + + /** Shows the resize-stack dialog. */ + void showResizeStackDialog() { + mCurrentDialogType = RESIZE_STACK_PICK_STACK_DIALOG; + show(mFragmentManager, TAG); + } + + /** Creates a new resize-stack pick-stack dialog. */ + private void createResizeStackPickStackDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + mStacks = ssp.getAllStackInfos(); + mTargetStackIndex.value = -1; + CharSequence[] stackNames = getAllStacksDescriptions(mStacks, -1, null); + builder.setTitle("Resize Stack - Pick stack"); + builder.setSingleChoiceItems(stackNames, mTargetStackIndex.value, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mTargetStackIndex.value = which; + } + }); + builder.setPositiveButton("Resize Stack", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (mTargetStackIndex.value != -1) { + // Prompt the user for the new dimensions + dismiss(); + mCurrentDialogType = RESIZE_STACK_DIALOG; + show(mFragmentManager, TAG); + } + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + } + + /** Creates a new resize-stack dialog. */ + private void createResizeStackDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + builder.setTitle("Resize Stack - Enter new dimensions"); + final ActivityManager.StackInfo stack = mStacks.valueAt(mTargetStackIndex.value); + mAddStackDialogContent = + inflater.inflate(R.layout.recents_multistack_stack_size_dialog, null, false); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_left, stack.bounds.left); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_top, stack.bounds.top); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_right, stack.bounds.right); + setDimensionInEditText(mAddStackDialogContent, R.id.inset_bottom, stack.bounds.bottom); + builder.setView(mAddStackDialogContent); + builder.setPositiveButton("Resize Stack", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int left = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_left); + int top = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_top); + int right = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_right); + int bottom = getDimensionFromEditText(mAddStackDialogContent, R.id.inset_bottom); + if (bottom <= top || right <= left) { + Toast.makeText(context, "Invalid dimensions", Toast.LENGTH_SHORT).show(); + dismiss(); + return; + } + ssp.resizeStack(stack.stackId, new Rect(left, top, right, bottom)); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + } + + /** Shows the remove-stack dialog. */ + void showRemoveStackDialog() { + mCurrentDialogType = REMOVE_STACK_DIALOG; + show(mFragmentManager, TAG); + } + + /** Shows the move-task dialog. */ + void showMoveTaskDialog(Task task) { + mCurrentDialogType = MOVE_TASK_DIALOG; + mTaskToMove = task; + show(mFragmentManager, TAG); + } + + /** Creates a new move-stack dialog. */ + private void createMoveTaskDialog(final Context context, LayoutInflater inflater, + AlertDialog.Builder builder, final SystemServicesProxy ssp) { + mStacks = ssp.getAllStackInfos(); + mTargetStackIndex.value = -1; + CharSequence[] stackNames = getAllStacksDescriptions(mStacks, mTaskToMove.key.stackId, + mTargetStackIndex); + builder.setTitle("Move Task to Stack"); + builder.setSingleChoiceItems(stackNames, mTargetStackIndex.value, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mTargetStackIndex.value = which; + } + }); + builder.setPositiveButton("Move Task", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (mTargetStackIndex.value != -1) { + ActivityManager.StackInfo toStack = mStacks.valueAt(mTargetStackIndex.value); + if (toStack.stackId != mTaskToMove.key.stackId) { + ssp.moveTaskToStack(mTaskToMove.key.id, toStack.stackId, true); + mTaskToMove.setStackId(toStack.stackId); + } + } + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + } + + /** Helper to get an integer value from an edit text. */ + private int getDimensionFromEditText(View container, int id) { + String text = ((EditText) container.findViewById(id)).getText().toString(); + if (text.trim().length() != 0) { + return Integer.parseInt(text.trim()); + } + return 0; + } + + /** Helper to set an integer value to an edit text. */ + private void setDimensionInEditText(View container, int id, int value) { + ((EditText) container.findViewById(id)).setText("" + value); + } + + /** Gets a list of all the stacks. */ + private CharSequence[] getAllStacksDescriptions(SparseArray<ActivityManager.StackInfo> stacks, + int targetStackId, MutableInt indexOfTargetStackId) { + int stackCount = stacks.size(); + CharSequence[] stackNames = new CharSequence[stackCount]; + for (int i = 0; i < stackCount; i++) { + ActivityManager.StackInfo stack = stacks.valueAt(i); + Rect b = stack.bounds; + String desc = "Stack " + stack.stackId + " / " + + "" + (stack.taskIds.length > 0 ? stack.taskIds.length : "No") + " tasks\n" + + "(" + b.left + ", " + b.top + ")-(" + b.right + ", " + b.bottom + ")\n"; + stackNames[i] = desc; + if (targetStackId != -1 && stack.stackId == targetStackId) { + indexOfTargetStackId.value = i; + } + } + return stackNames; + } + + @Override + public Dialog onCreateDialog(Bundle args) { + final Context context = this.getActivity(); + final SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); + LayoutInflater inflater = getActivity().getLayoutInflater(); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + switch(mCurrentDialogType) { + case ADD_STACK_DIALOG: + createAddStackDialog(context, inflater, builder, ssp); + break; + case ADD_STACK_PICK_APP_DIALOG: + createAddStackPickAppDialog(context, inflater, builder, ssp); + break; + case MOVE_TASK_DIALOG: + createMoveTaskDialog(context, inflater, builder, ssp); + break; + case RESIZE_STACK_PICK_STACK_DIALOG: + createResizeStackPickStackDialog(context, inflater, builder, ssp); + break; + case RESIZE_STACK_DIALOG: + createResizeStackDialog(context, inflater, builder, ssp); + break; + } + return builder.create(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 237d4f0..72040fe 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.ActivityOptions; import android.app.AppGlobals; +import android.app.IActivityContainer; import android.app.IActivityManager; import android.app.ITaskStackListener; import android.app.SearchManager; @@ -49,10 +50,12 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.Pair; +import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; @@ -64,6 +67,8 @@ import com.android.systemui.recents.Recents; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Random; @@ -228,6 +233,23 @@ public class SystemServicesProxy { return null; } + /** Returns a list of all the launcher apps sorted by name. */ + public List<ResolveInfo> getLauncherApps() { + if (mPm == null) return new ArrayList<ResolveInfo>(); + + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List<ResolveInfo> activities = mPm.queryIntentActivities(mainIntent, 0 /* flags */); + Collections.sort(activities, new Comparator<ResolveInfo>() { + @Override + public int compare(ResolveInfo o1, ResolveInfo o2) { + return getActivityLabel(o1.activityInfo).compareTo( + getActivityLabel(o2.activityInfo)); + } + }); + return activities; + } + /** Returns whether the recents is currently running */ public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost) { @@ -250,6 +272,64 @@ public class SystemServicesProxy { return false; } + /** Create a new stack. */ + public void createNewStack(int displayId, Rect bounds, Intent activity) { + try { + IActivityContainer container = mIam.createStackOnDisplay(displayId); + if (container != null) { + // Resize the stack + resizeStack(container.getStackId(), bounds); + // Start the new activity on that stack + container.startActivity(activity); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + /** Resizes a stack. */ + public void resizeStack(int stackId, Rect bounds) { + if (mIam == null) return; + + try { + mIam.resizeStack(stackId, bounds); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + /** Returns the stack info for all stacks. */ + public SparseArray<ActivityManager.StackInfo> getAllStackInfos() { + if (mIam == null) return new SparseArray<ActivityManager.StackInfo>(); + + try { + SparseArray<ActivityManager.StackInfo> stacks = + new SparseArray<ActivityManager.StackInfo>(); + List<ActivityManager.StackInfo> infos = mIam.getAllStackInfos(); + int stackCount = infos.size(); + for (int i = 0; i < stackCount; i++) { + ActivityManager.StackInfo info = infos.get(i); + stacks.put(info.stackId, info); + } + return stacks; + } catch (RemoteException e) { + e.printStackTrace(); + return new SparseArray<ActivityManager.StackInfo>(); + } + } + + /** Returns the focused stack id. */ + public int getFocusedStack() { + if (mIam == null) return -1; + + try { + return mIam.getFocusedStackId(); + } catch (RemoteException e) { + e.printStackTrace(); + return -1; + } + } + /** Returns whether the specified task is in the home stack */ public boolean isInHomeStack(int taskId) { if (mAm == null) return false; @@ -313,7 +393,7 @@ public class SystemServicesProxy { return thumbnail; } - /** Moves a task to the front with the specified activity options */ + /** Moves a task to the front with the specified activity options. */ public void moveTaskToFront(int taskId, ActivityOptions opts) { if (mAm == null) return; if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; @@ -326,6 +406,18 @@ public class SystemServicesProxy { } } + /** Moves a task to another stack. */ + public void moveTaskToStack(int taskId, int stackId, boolean toTop) { + if (mIam == null) return; + if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; + + try { + mIam.moveTaskToStack(taskId, stackId, toTop); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + /** Removes the task */ public void removeTask(int taskId) { if (mAm == null) return; @@ -524,6 +616,13 @@ public class SystemServicesProxy { } /** + * Returns a system property. + */ + public String getSystemProperty(String key) { + return SystemProperties.get(key); + } + + /** * Returns the window rect. */ public Rect getWindowRect() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 3d25c80..788e473 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -20,9 +20,12 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.util.Log; +import android.util.SparseArray; +import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -60,7 +63,7 @@ public class RecentsTaskLoadPlan { SystemServicesProxy mSystemServicesProxy; List<ActivityManager.RecentTaskInfo> mRawTasks; - TaskStack mStack; + SparseArray<TaskStack> mStacks = new SparseArray<>(); HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache = new HashMap<Task.ComponentNameKey, ActivityInfoHandle>(); @@ -90,21 +93,28 @@ public class RecentsTaskLoadPlan { synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) { if (DEBUG) Log.d(TAG, "preloadPlan"); + // This activity info cache will be used for both preloadPlan() and executePlan() mActivityInfoCache.clear(); - mStack = new TaskStack(); + + // TODO (multi-display): Currently assume the primary display + Rect displayBounds = mSystemServicesProxy.getWindowRect(); Resources res = mContext.getResources(); - ArrayList<Task> loadedTasks = new ArrayList<Task>(); + SparseArray<ArrayList<Task>> stacksTasks = new SparseArray<>(); if (mRawTasks == null) { preloadRawTasks(isTopTaskHome); } + int firstStackId = -1; int taskCount = mRawTasks.size(); for (int i = 0; i < taskCount; i++) { ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + if (firstStackId < 0) { + firstStackId = t.stackId; + } // Compose the task key - Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId, - t.firstActiveTime, t.lastActiveTime); + Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent, + t.userId, t.firstActiveTime, t.lastActiveTime); // Get an existing activity info handle if possible Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); @@ -143,14 +153,42 @@ public class RecentsTaskLoadPlan { iconFilename); task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false); if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail); - loadedTasks.add(task); + + if (!mConfig.multiStackEnabled || + Constants.DebugFlags.App.EnableMultiStackToSingleStack) { + ArrayList<Task> stackTasks = stacksTasks.get(firstStackId); + if (stackTasks == null) { + stackTasks = new ArrayList<Task>(); + stacksTasks.put(firstStackId, stackTasks); + } + stackTasks.add(task); + } else { + ArrayList<Task> stackTasks = stacksTasks.get(t.stackId); + if (stackTasks == null) { + stackTasks = new ArrayList<Task>(); + stacksTasks.put(t.stackId, stackTasks); + } + stackTasks.add(task); + } } - mStack.setTasks(loadedTasks); - mStack.createAffiliatedGroupings(mConfig); - // Assertion - if (mStack.getTaskCount() != mRawTasks.size()) { - throw new RuntimeException("Loading failed"); + // Initialize the stacks + SparseArray<ActivityManager.StackInfo> stackInfos = mSystemServicesProxy.getAllStackInfos(); + mStacks.clear(); + int stackCount = stacksTasks.size(); + for (int i = 0; i < stackCount; i++) { + int stackId = stacksTasks.keyAt(i); + ActivityManager.StackInfo info = stackInfos.get(stackId); + ArrayList<Task> stackTasks = stacksTasks.valueAt(i); + TaskStack stack = new TaskStack(stackId); + if (Constants.DebugFlags.App.EnableMultiStackToSingleStack) { + stack.setBounds(displayBounds, displayBounds); + } else { + stack.setBounds(info.bounds, displayBounds); + } + stack.setTasks(stackTasks); + stack.createAffiliatedGroupings(mConfig); + mStacks.put(stackId, stack); } } @@ -166,72 +204,93 @@ public class RecentsTaskLoadPlan { Resources res = mContext.getResources(); // Iterate through each of the tasks and load them according to the load conditions. - ArrayList<Task> tasks = mStack.getTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = mRawTasks.get(i); - Task task = tasks.get(i); - Task.TaskKey taskKey = task.key; + int stackCount = mStacks.size(); + for (int j = 0; j < stackCount; j++) { + ArrayList<Task> tasks = mStacks.valueAt(j).getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + ActivityManager.RecentTaskInfo t = mRawTasks.get(i); + Task task = tasks.get(i); + Task.TaskKey taskKey = task.key; - // Get an existing activity info handle if possible - Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); - ActivityInfoHandle infoHandle; - boolean hadCachedActivityInfo = false; - if (mActivityInfoCache.containsKey(cnKey)) { - infoHandle = mActivityInfoCache.get(cnKey); - hadCachedActivityInfo = true; - } else { - infoHandle = new ActivityInfoHandle(); - } + // Get an existing activity info handle if possible + Task.ComponentNameKey cnKey = taskKey.getComponentNameKey(); + ActivityInfoHandle infoHandle; + boolean hadCachedActivityInfo = false; + if (mActivityInfoCache.containsKey(cnKey)) { + infoHandle = mActivityInfoCache.get(cnKey); + hadCachedActivityInfo = true; + } else { + infoHandle = new ActivityInfoHandle(); + } - boolean isRunningTask = (task.key.id == opts.runningTaskId); - boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); - boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); + boolean isRunningTask = (task.key.id == opts.runningTaskId); + boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks); + boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails); - // If requested, skip the running task - if (opts.onlyLoadPausedActivities && isRunningTask) { - continue; - } + // If requested, skip the running task + if (opts.onlyLoadPausedActivities && isRunningTask) { + continue; + } - if (opts.loadIcons && (isRunningTask || isVisibleTask)) { - if (task.activityIcon == null) { - if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); - task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, - mSystemServicesProxy, res, infoHandle, true); + if (opts.loadIcons && (isRunningTask || isVisibleTask)) { + if (task.activityIcon == null) { + if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); + task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, + t.taskDescription, mSystemServicesProxy, res, infoHandle, true); + } } - } - if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { - if (task.thumbnail == null || isRunningTask) { - if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); - if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, - true); - } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { - loadQueue.addTask(task); + if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { + if (task.thumbnail == null || isRunningTask) { + if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); + if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { + task.thumbnail = loader.getAndUpdateThumbnail(taskKey, + mSystemServicesProxy, true); + } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { + loadQueue.addTask(task); + } } } - } - // Update the activity info cache - if (!hadCachedActivityInfo && infoHandle.info != null) { - mActivityInfoCache.put(cnKey, infoHandle); + // Update the activity info cache + if (!hadCachedActivityInfo && infoHandle.info != null) { + mActivityInfoCache.put(cnKey, infoHandle); + } } } } /** - * Composes and returns a TaskStack from the preloaded list of recent tasks. + * Returns all TaskStacks from the preloaded list of recent tasks. */ - public TaskStack getTaskStack() { - return mStack; + public ArrayList<TaskStack> getAllTaskStacks() { + ArrayList<TaskStack> stacks = new ArrayList<TaskStack>(); + int stackCount = mStacks.size(); + for (int i = 0; i < stackCount; i++) { + stacks.add(mStacks.valueAt(i)); + } + // Ensure that we have at least one stack + if (stacks.isEmpty()) { + stacks.add(new TaskStack()); + } + return stacks; } /** - * Composes and returns a SpaceNode from the preloaded list of recent tasks. + * Returns a specific TaskStack from the preloaded list of recent tasks. */ - public SpaceNode getSpaceNode() { - SpaceNode node = new SpaceNode(); - node.setStack(mStack); - return node; + public TaskStack getTaskStack(int stackId) { + return mStacks.get(stackId); + } + + /** Returns whether there are any tasks in any stacks. */ + public boolean hasTasks() { + int stackCount = mStacks.size(); + for (int i = 0; i < stackCount; i++) { + if (mStacks.valueAt(i).getTaskCount() > 0) { + return true; + } + } + return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java deleted file mode 100644 index 831698a..0000000 --- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recents.model; - -import android.graphics.Rect; - -import java.util.ArrayList; - - -/** - * The full recents space is partitioned using a BSP into various nodes that define where task - * stacks should be placed. - */ -public class SpaceNode { - /* BSP node callbacks */ - public interface SpaceNodeCallbacks { - /** Notifies when a node is added */ - public void onSpaceNodeAdded(SpaceNode node); - /** Notifies when a node is measured */ - public void onSpaceNodeMeasured(SpaceNode node, Rect rect); - } - - SpaceNode mStartNode; - SpaceNode mEndNode; - - TaskStack mStack; - - public SpaceNode() { - // Do nothing - } - - /** Sets the current stack for this space node */ - public void setStack(TaskStack stack) { - mStack = stack; - } - - /** Returns the task stack (not null if this is a leaf) */ - TaskStack getStack() { - return mStack; - } - - /** Returns whether there are any tasks in any stacks below this node. */ - public boolean hasTasks() { - return (mStack.getTaskCount() > 0) || - (mStartNode != null && mStartNode.hasTasks()) || - (mEndNode != null && mEndNode.hasTasks()); - } - - /** Returns whether this is a leaf node */ - boolean isLeafNode() { - return (mStartNode == null) && (mEndNode == null); - } - - /** Returns all the descendent task stacks */ - private void getStacksRec(ArrayList<TaskStack> stacks) { - if (isLeafNode()) { - stacks.add(mStack); - } else { - mStartNode.getStacksRec(stacks); - mEndNode.getStacksRec(stacks); - } - } - public ArrayList<TaskStack> getStacks() { - ArrayList<TaskStack> stacks = new ArrayList<TaskStack>(); - getStacksRec(stacks); - return stacks; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 55dfe45..0cd55d7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -36,6 +36,9 @@ public class Task { public void onTaskDataLoaded(); /* Notifies when a task has been unbound */ public void onTaskDataUnloaded(); + + /* Notifies when a task's stack id has changed. */ + public void onMultiStackDebugTaskStackIdChanged(); } /** The ComponentNameKey represents the unique primary key for a component @@ -68,14 +71,17 @@ public class Task { public static class TaskKey { final ComponentNameKey mComponentNameKey; public final int id; + public int stackId; public final Intent baseIntent; public final int userId; public long firstActiveTime; public long lastActiveTime; - public TaskKey(int id, Intent intent, int userId, long firstActiveTime, long lastActiveTime) { + public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime, + long lastActiveTime) { mComponentNameKey = new ComponentNameKey(intent.getComponent(), userId); this.id = id; + this.stackId = stackId; this.baseIntent = intent; this.userId = userId; this.firstActiveTime = firstActiveTime; @@ -92,18 +98,19 @@ public class Task { if (!(o instanceof TaskKey)) { return false; } - return id == ((TaskKey) o).id - && userId == ((TaskKey) o).userId; + TaskKey otherKey = (TaskKey) o; + return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId; } @Override public int hashCode() { - return (id << 5) + userId; + return Objects.hash(id, stackId, userId); } @Override public String toString() { return "Task.Key: " + id + ", " + + "s: " + stackId + ", " + "u: " + userId + ", " + "lat: " + lastActiveTime + ", " + baseIntent.getComponent().getPackageName(); @@ -180,6 +187,14 @@ public class Task { this.group = group; } + /** Updates the stack id of this task. */ + public void setStackId(int stackId) { + key.stackId = stackId; + if (mCb != null) { + mCb.onMultiStackDebugTaskStackIdChanged(); + } + } + /** Notifies the callback listeners that this task has been loaded */ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) { this.applicationIcon = applicationIcon; diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 7f7eee4..5aaea15 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.model; import android.graphics.Color; +import android.graphics.Rect; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.NamedCounter; @@ -173,33 +174,38 @@ public class TaskStack { public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks); } - /** A pair of indices representing the group and task positions in the stack and group. */ - public static class GroupTaskIndex { - public int groupIndex; // Index in the stack - public int taskIndex; // Index in the group - - public GroupTaskIndex() {} - - public GroupTaskIndex(int gi, int ti) { - groupIndex = gi; - taskIndex = ti; - } - } - // The task offset to apply to a task id as a group affiliation static final int IndividualTaskIdOffset = 1 << 16; + public final int id; + public final Rect stackBounds = new Rect(); + public final Rect displayBounds = new Rect(); + FilteredTaskList mTaskList = new FilteredTaskList(); TaskStackCallbacks mCb; ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>(); HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>(); - /** Sets the callbacks for this task stack */ + public TaskStack() { + this(0); + } + + public TaskStack(int stackId) { + id = stackId; + } + + /** Sets the callbacks for this task stack. */ public void setCallbacks(TaskStackCallbacks cb) { mCb = cb; } + /** Sets the bounds of this stack. */ + public void setBounds(Rect stackBounds, Rect displayBounds) { + this.stackBounds.set(stackBounds); + this.displayBounds.set(displayBounds); + } + /** Resets this TaskStack. */ public void reset() { mCb = null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 5152150..1bed553 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -29,8 +29,10 @@ import android.provider.Settings; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewStub; import android.view.WindowInsets; import android.widget.FrameLayout; +import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -40,6 +42,7 @@ import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -56,13 +59,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV public void onAllTaskViewsDismissed(); public void onExitToHomeAnimationTriggered(); public void onScreenPinningRequest(); + + public void onMultiStackAddStack(); + public void onMultiStackResizeStack(); + public void onMultiStackRemoveStack(); + public void onMultiStackMoveTask(Task t); } RecentsConfiguration mConfig; LayoutInflater mInflater; DebugOverlayView mDebugOverlay; + ViewStub mMultiStackDebugStub; + View mMultiStackDebugView; + RecentsViewLayoutAlgorithm mLayoutAlgorithm; ArrayList<TaskStack> mStacks; + List<TaskStackView> mImmutableTaskStackViews = new ArrayList<TaskStackView>(); View mSearchBar; RecentsViewCallbacks mCb; @@ -82,6 +94,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV super(context, attrs, defStyleAttr, defStyleRes); mConfig = RecentsConfiguration.getInstance(); mInflater = LayoutInflater.from(context); + mLayoutAlgorithm = new RecentsViewLayoutAlgorithm(mConfig); + } + + @Override + protected void onFinishInflate() { + if (!mConfig.multiStackEnabled) return; + + mMultiStackDebugStub = (ViewStub) findViewById(R.id.multistack_debug_view_stub); + if (mMultiStackDebugView == null) { + mMultiStackDebugView = mMultiStackDebugStub.inflate(); + mMultiStackDebugView.findViewById(R.id.add_stack).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCb.onMultiStackAddStack(); + } + }); + mMultiStackDebugView.findViewById(R.id.resize_stack).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCb.onMultiStackResizeStack(); + } + }); + } } /** Sets the callbacks */ @@ -99,24 +134,19 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV int numStacks = stacks.size(); // Make a list of the stack view children only - ArrayList<TaskStackView> stackViews = new ArrayList<TaskStackView>(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - stackViews.add((TaskStackView) child); - } - } + ArrayList<TaskStackView> stackViewsList = new ArrayList<TaskStackView>(); + List<TaskStackView> stackViews = getTaskStackViews(); // Remove all/extra stack views int numTaskStacksToKeep = 0; // Keep no tasks if we are recreating the layout if (mConfig.launchedReuseTaskStackViews) { - numTaskStacksToKeep = Math.min(childCount, numStacks); + numTaskStacksToKeep = Math.min(stackViews.size(), numStacks); } for (int i = stackViews.size() - 1; i >= numTaskStacksToKeep; i--) { removeView(stackViews.get(i)); stackViews.remove(i); } + stackViewsList.addAll(stackViews); // Update the stack views that we are keeping for (int i = 0; i < numTaskStacksToKeep; i++) { @@ -133,42 +163,51 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV TaskStackView stackView = new TaskStackView(getContext(), stack); stackView.setCallbacks(this); addView(stackView); + stackViewsList.add(stackView); } + // Set the immutable stack views list + mImmutableTaskStackViews = Collections.unmodifiableList(stackViewsList); + // Enable debug mode drawing on all the stacks if necessary if (mConfig.debugModeEnabled) { - for (int i = childCount - 1; i >= 0; i--) { - View v = getChildAt(i); - if (v != mSearchBar) { - TaskStackView stackView = (TaskStackView) v; - stackView.setDebugOverlay(mDebugOverlay); - } + for (int i = mImmutableTaskStackViews.size() - 1; i >= 0; i--) { + TaskStackView stackView = mImmutableTaskStackViews.get(i); + stackView.setDebugOverlay(mDebugOverlay); } } + // Bring the debug view to the front + if (mMultiStackDebugView != null) { + mMultiStackDebugView.bringToFront(); + } + // Trigger a new layout requestLayout(); } + /** Gets the list of task views */ + List<TaskStackView> getTaskStackViews() { + return mImmutableTaskStackViews; + } + /** Launches the focused task from the first stack if possible */ public boolean launchFocusedTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - TaskStack stack = stackView.mStack; - // Iterate the stack views and try and find the focused task - List<TaskView> taskViews = stackView.getTaskViews(); - int taskViewCount = taskViews.size(); - for (int j = 0; j < taskViewCount; j++) { - TaskView tv = taskViews.get(j); - Task task = tv.getTask(); - if (tv.isFocusedTask()) { - onTaskViewClicked(stackView, tv, stack, task, false); - return true; - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + TaskStack stack = stackView.getStack(); + // Iterate the stack views and try and find the focused task + List<TaskView> taskViews = stackView.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int j = 0; j < taskViewCount; j++) { + TaskView tv = taskViews.get(j); + Task task = tv.getTask(); + if (tv.isFocusedTask()) { + onTaskViewClicked(stackView, tv, stack, task, false); + return true; } } } @@ -178,24 +217,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Launches the task that Recents was launched from, if possible */ public boolean launchPreviousTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - TaskStack stack = stackView.mStack; - ArrayList<Task> tasks = stack.getTasks(); - - // Find the launch task in the stack - if (!tasks.isEmpty()) { - int taskCount = tasks.size(); - for (int j = 0; j < taskCount; j++) { - if (tasks.get(j).isLaunchTarget) { - Task task = tasks.get(j); - TaskView tv = stackView.getChildViewForTask(task); - onTaskViewClicked(stackView, tv, stack, task, false); - return true; - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + TaskStack stack = stackView.getStack(); + ArrayList<Task> tasks = stack.getTasks(); + + // Find the launch task in the stack + if (!tasks.isEmpty()) { + int taskCount = tasks.size(); + for (int j = 0; j < taskCount; j++) { + if (tasks.get(j).isLaunchTarget) { + Task task = tasks.get(j); + TaskView tv = stackView.getChildViewForTask(task); + onTaskViewClicked(stackView, tv, stack, task, false); + return true; } } } @@ -209,13 +246,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // to ensure that it runs ctx.postAnimationTrigger.increment(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.startEnterRecentsAnimation(ctx); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.startEnterRecentsAnimation(ctx); } ctx.postAnimationTrigger.decrement(); } @@ -225,13 +260,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // We have to increment/decrement the post animation trigger in case there are no children // to ensure that it runs ctx.postAnimationTrigger.increment(); - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.startExitToHomeAnimation(ctx); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.startExitToHomeAnimation(ctx); } ctx.postAnimationTrigger.decrement(); @@ -287,22 +320,31 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } Rect taskStackBounds = new Rect(); - mConfig.getTaskStackBounds(width, height, mConfig.systemInsets.top, + mConfig.getAvailableTaskStackBounds(width, height, mConfig.systemInsets.top, mConfig.systemInsets.right, taskStackBounds); - // Measure each TaskStackView with the full width and height of the window since the + // Measure each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar && child.getVisibility() != GONE) { - TaskStackView tsv = (TaskStackView) child; - // Set the insets to be the top/left inset + search bounds - tsv.setStackInsetRect(taskStackBounds); - tsv.measure(widthMeasureSpec, heightMeasureSpec); + List<TaskStackView> stackViews = getTaskStackViews(); + List<Rect> stackViewsBounds = mLayoutAlgorithm.computeStackRects(stackViews, + taskStackBounds); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + if (stackView.getVisibility() != GONE) { + // We are going to measure the TaskStackView with the whole RecentsView dimensions, + // but the actual stack is going to be inset to the bounds calculated by the layout + // algorithm + stackView.setStackInsetRect(stackViewsBounds.get(i)); + stackView.measure(widthMeasureSpec, heightMeasureSpec); } } + // Measure the multistack debug view + if (mMultiStackDebugView != null) { + mMultiStackDebugView.measure(width, height); + } + setMeasuredDimension(width, height); } @@ -322,14 +364,27 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Layout each TaskStackView with the full width and height of the window since the // transition view is a child of that stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar && child.getVisibility() != GONE) { - child.layout(left, top, left + child.getMeasuredWidth(), - top + child.getMeasuredHeight()); + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + if (stackView.getVisibility() != GONE) { + stackView.layout(left, top, left + stackView.getMeasuredWidth(), + top + stackView.getMeasuredHeight()); } } + + // Layout the multistack debug view + if (mMultiStackDebugView != null) { + Rect taskStackBounds = new Rect(); + mConfig.getAvailableTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), + mConfig.systemInsets.top, mConfig.systemInsets.right, taskStackBounds); + mMultiStackDebugView.layout(left, + taskStackBounds.bottom - mConfig.systemInsets.bottom - + mMultiStackDebugView.getMeasuredHeight(), + left + mMultiStackDebugView.getMeasuredWidth(), + taskStackBounds.bottom - mConfig.systemInsets.bottom); + } } @Override @@ -343,41 +398,29 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Notifies each task view of the user interaction. */ public void onUserInteraction() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onUserInteraction(); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onUserInteraction(); } } /** Focuses the next task in the first stack view */ public void focusNextTask(boolean forward) { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.focusNextTask(forward, true); - break; - } + List<TaskStackView> stackViews = getTaskStackViews(); + if (!stackViews.isEmpty()) { + stackViews.get(0).focusNextTask(forward, true); } } /** Dismisses the focused task. */ public void dismissFocusedTask() { // Get the first stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.dismissFocusedTask(); - break; - } + List<TaskStackView> stackViews = getTaskStackViews(); + if (!stackViews.isEmpty()) { + stackViews.get(0).dismissFocusedTask(); } } @@ -476,9 +519,16 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } }; } - opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, - b, offsetX, offsetY, transform.rect.width(), transform.rect.height(), - sourceView.getHandler(), animStartedListener); + if (mConfig.multiStackEnabled) { + opts = ActivityOptions.makeCustomAnimation(sourceView.getContext(), + R.anim.recents_from_unknown_enter, + R.anim.recents_from_unknown_exit, + sourceView.getHandler(), animStartedListener); + } else { + opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView, + b, offsetX, offsetY, transform.rect.width(), transform.rect.height(), + sourceView.getHandler(), animStartedListener); + } } final ActivityOptions launchOpts = opts; @@ -561,13 +611,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Final callback after Recents is finally hidden. */ public void onRecentsHidden() { // Notify each task stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onRecentsHidden(); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onRecentsHidden(); } } @@ -599,18 +647,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } } + @Override + public void onMultiStackMoveTask(Task t) { + if (mCb != null) { + mCb.onMultiStackMoveTask(t); + } + } + /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/ @Override public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) { // Propagate this event down to each task stack view - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child != mSearchBar) { - TaskStackView stackView = (TaskStackView) child; - stackView.onPackagesChanged(monitor, packageName, userId); - } + List<TaskStackView> stackViews = getTaskStackViews(); + int stackCount = stackViews.size(); + for (int i = 0; i < stackCount; i++) { + TaskStackView stackView = stackViews.get(i); + stackView.onPackagesChanged(monitor, packageName, userId); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java new file mode 100644 index 0000000..eea273c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewLayoutAlgorithm.java @@ -0,0 +1,59 @@ +/* + * 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.systemui.recents.views; + +import android.graphics.Rect; +import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.model.TaskStack; + +import java.util.ArrayList; +import java.util.List; + +/* The layout logic for the RecentsView. */ +public class RecentsViewLayoutAlgorithm { + + RecentsConfiguration mConfig; + + public RecentsViewLayoutAlgorithm(RecentsConfiguration config) { + mConfig = config; + } + + /** Return the relative coordinate given coordinates in another size. */ + private int getRelativeCoordinate(int availableOffset, int availableSize, int otherCoord, int otherSize) { + float relPos = (float) otherCoord / otherSize; + return availableOffset + (int) (relPos * availableSize); + } + + /** + * Computes and returns the bounds that each of the stack views should take up. + */ + List<Rect> computeStackRects(List<TaskStackView> stackViews, Rect availableBounds) { + ArrayList<Rect> bounds = new ArrayList<Rect>(stackViews.size()); + int stackViewsCount = stackViews.size(); + for (int i = 0; i < stackViewsCount; i++) { + TaskStack stack = stackViews.get(i).getStack(); + Rect sb = stack.stackBounds; + Rect db = stack.displayBounds; + Rect ab = availableBounds; + bounds.add(new Rect(getRelativeCoordinate(ab.left, ab.width(), sb.left, db.width()), + getRelativeCoordinate(ab.top, ab.height(), sb.top, db.height()), + getRelativeCoordinate(ab.left, ab.width(), sb.right, db.width()), + getRelativeCoordinate(ab.top, ab.height(), sb.bottom, db.height()))); + } + return bounds; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 290792a..2318319 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -60,6 +60,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks); public void onTaskStackFilterTriggered(); public void onTaskStackUnfilterTriggered(); + + public void onMultiStackMoveTask(Task t); } RecentsConfiguration mConfig; @@ -149,6 +151,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal requestLayout(); } + /** Returns the task stack. */ + TaskStack getStack() { + return mStack; + } + /** Sets the debug overlay */ public void setDebugOverlay(DebugOverlayView overlay) { mDebugOverlay = overlay; @@ -625,6 +632,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return mTouchHandler.onGenericMotionEvent(ev); } + /** Returns the region that touch gestures can be started in. */ + Rect getTouchableRegion() { + return mTaskStackBounds; + } + @Override public void computeScroll() { mStackScroller.computeScroll(); @@ -1326,6 +1338,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + @Override + public void onMultiStackMoveTask(TaskView tv) { + if (mCb != null) { + mCb.onMultiStackMoveTask(tv.getTask()); + } + } + /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index ccad2f1..fabc86d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -96,16 +96,6 @@ public class TaskStackViewScroller { } return false; } - /** Bounds the current scroll if necessary, but does not synchronize the stack view with the model. */ - public boolean boundScrollRaw() { - float curScroll = getStackScroll(); - float newScroll = getBoundedStackScroll(curScroll); - if (Float.compare(newScroll, curScroll) != 0) { - setStackScrollRaw(newScroll); - return true; - } - return false; - } /** Returns the bounded stack scroll */ float getBoundedStackScroll(float scroll) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 4a6112c..6cdddc5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -123,6 +123,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return false; } + int action = ev.getAction(); + if (mConfig.multiStackEnabled) { + // Check if we are within the bounds of the stack view contents + if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) { + return false; + } + } + } + // Pass through to swipe helper if we are swiping mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); if (mInterceptedBySwipeHelper) { @@ -131,7 +141,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { boolean wasScrolling = mScroller.isScrolling() || (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning()); - int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { // Save the touch down info @@ -198,6 +207,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return false; } + int action = ev.getAction(); + if (mConfig.multiStackEnabled) { + // Check if we are within the bounds of the stack view contents + if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + if (!mSv.getTouchableRegion().contains((int) ev.getX(), (int) ev.getY())) { + return false; + } + } + } + // Pass through to swipe helper if we are swiping if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { return true; @@ -206,7 +225,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Update the velocity tracker initVelocityTrackerIfNotExists(); - int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { // Save the touch down info diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 82120bf..098f2f9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -45,6 +45,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, public void onTaskViewDismissed(TaskView tv); public void onTaskViewClipStateChanged(TaskView tv); public void onTaskViewFocusChanged(TaskView tv, boolean focused); + + public void onMultiStackMoveTask(TaskView tv); } RecentsConfiguration mConfig; @@ -457,6 +459,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, .start(); } + /** Enables/disables handling touch on this task view. */ + void setTouchEnabled(boolean enabled) { + setOnClickListener(enabled ? this : null); + } + /** Animates this task view if the user does not interact with the stack after a certain time. */ void startNoUserInteractionAnimation() { mHeaderView.startNoUserInteractionAnimation(); @@ -667,6 +674,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, // Rebind any listeners mHeaderView.mApplicationIcon.setOnClickListener(this); mHeaderView.mDismissButton.setOnClickListener(this); + if (mConfig.multiStackEnabled) { + mHeaderView.mMoveTaskButton.setOnClickListener(this); + } mActionButtonView.setOnClickListener(this); if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { if (mConfig.developerOptionsEnabled) { @@ -687,6 +697,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, // Unbind any listeners mHeaderView.mApplicationIcon.setOnClickListener(null); mHeaderView.mDismissButton.setOnClickListener(null); + if (mConfig.multiStackEnabled) { + mHeaderView.mMoveTaskButton.setOnClickListener(null); + } mActionButtonView.setOnClickListener(null); if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { mHeaderView.mApplicationIcon.setOnLongClickListener(null); @@ -695,9 +708,9 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, mTaskDataLoaded = false; } - /** Enables/disables handling touch on this task view. */ - void setTouchEnabled(boolean enabled) { - setOnClickListener(enabled ? this : null); + @Override + public void onMultiStackDebugTaskStackIdChanged() { + mHeaderView.rebindToTask(mTask); } /**** View.OnClickListener Implementation ****/ @@ -717,6 +730,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } } else if (v == mHeaderView.mDismissButton) { dismissTask(); + } else if (v == mHeaderView.mMoveTaskButton) { + if (mCb != null) { + mCb.onMultiStackMoveTask(tv); + } } } }, 125); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index e13eed8..b827acc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -56,6 +56,7 @@ public class TaskViewHeader extends FrameLayout { RecentsConfiguration mConfig; // Header views + ImageView mMoveTaskButton; ImageView mDismissButton; ImageView mApplicationIcon; TextView mActivityDescription; @@ -126,6 +127,10 @@ public class TaskViewHeader extends FrameLayout { mApplicationIcon = (ImageView) findViewById(R.id.application_icon); mActivityDescription = (TextView) findViewById(R.id.activity_description); mDismissButton = (ImageView) findViewById(R.id.dismiss_task); + mMoveTaskButton = (ImageView) findViewById(R.id.move_task); + if (mConfig.multiStackEnabled) { + mMoveTaskButton.setVisibility(View.VISIBLE); + } // Hide the backgrounds if they are ripple drawables if (!Constants.DebugFlags.App.EnableTaskFiltering) { @@ -188,7 +193,10 @@ public class TaskViewHeader extends FrameLayout { mApplicationIcon.setImageDrawable(t.applicationIcon); } mApplicationIcon.setContentDescription(t.activityLabel); - if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { + // Always update when multi stack debugging is enabled as the stack id can change + if (mConfig.multiStackEnabled) { + mActivityDescription.setText("[" + t.key.stackId + "] " + t.activityLabel); + } else if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { mActivityDescription.setText(t.activityLabel); } // Try and apply the system ui tint diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index a2ff64a..747e702 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -148,7 +148,8 @@ public class NotificationPanelView extends PanelView implements private boolean mBlockTouches; private int mNotificationScrimWaitDistance; - private boolean mTwoFingerQsExpand; + // Used for two finger gesture as well as accessibility shortcut to QS. + private boolean mQsExpandImmediate; private boolean mTwoFingerQsExpandPossible; /** @@ -475,6 +476,13 @@ public class NotificationPanelView extends PanelView implements } } + public void expandWithQs() { + if (mQsExpansionEnabled) { + mQsExpandImmediate = true; + } + expand(); + } + @Override public void fling(float vel, boolean expand) { GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); @@ -658,7 +666,7 @@ public class NotificationPanelView extends PanelView implements if (mExpandedHeight != 0) { handleQsDown(event); } - if (!mTwoFingerQsExpand && mQsTracking) { + if (!mQsExpandImmediate && mQsTracking) { onQsTouch(event); if (!mConflictingQsExpansionGesture) { return true; @@ -675,7 +683,7 @@ public class NotificationPanelView extends PanelView implements if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN && event.getPointerCount() == 2 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { - mTwoFingerQsExpand = true; + mQsExpandImmediate = true; requestPanelHeightUpdate(); // Normally, we start listening when the panel is expanded, but here we need to start @@ -1166,7 +1174,7 @@ public class NotificationPanelView extends PanelView implements private float calculateQsTopPadding() { if (mKeyguardShowing - && (mTwoFingerQsExpand || mIsExpanding && mQsExpandedWhenExpandingStarted)) { + && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) { // Either QS pushes the notifications down when fully expanded, or QS is fully above the // notifications (mostly on tablets). maxNotifications denotes the normal top padding @@ -1200,7 +1208,7 @@ public class NotificationPanelView extends PanelView implements mScrollView.getScrollY(), mAnimateNextTopPaddingChange || animate, mKeyguardShowing - && (mTwoFingerQsExpand || mIsExpanding && mQsExpandedWhenExpandingStarted)); + && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)); mAnimateNextTopPaddingChange = false; } @@ -1313,7 +1321,7 @@ public class NotificationPanelView extends PanelView implements min = Math.max(min, minHeight); } int maxHeight; - if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { + if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { maxHeight = calculatePanelHeightQsExpanded(); } else { maxHeight = calculatePanelHeightShade(); @@ -1328,10 +1336,10 @@ public class NotificationPanelView extends PanelView implements @Override protected void onHeightUpdated(float expandedHeight) { - if (!mQsExpanded || mTwoFingerQsExpand || mIsExpanding && mQsExpandedWhenExpandingStarted) { + if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { positionClockAndNotifications(); } - if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null + if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll) { float t; if (mKeyguardShowing) { @@ -1555,7 +1563,7 @@ public class NotificationPanelView extends PanelView implements } else { setListening(true); } - mTwoFingerQsExpand = false; + mQsExpandImmediate = false; mTwoFingerQsExpandPossible = false; } @@ -1573,7 +1581,7 @@ public class NotificationPanelView extends PanelView implements @Override protected void setOverExpansion(float overExpansion, boolean isPixels) { - if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) { + if (mConflictingQsExpansionGesture || mQsExpandImmediate) { return; } if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { @@ -1593,7 +1601,7 @@ public class NotificationPanelView extends PanelView implements protected void onTrackingStarted() { super.onTrackingStarted(); if (mQsFullyExpanded) { - mTwoFingerQsExpand = true; + mQsExpandImmediate = true; } if (mStatusBar.getBarState() == StatusBarState.KEYGUARD || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { @@ -1817,7 +1825,7 @@ public class NotificationPanelView extends PanelView implements @Override protected boolean fullyExpandedClearAllVisible() { return mNotificationStackScroller.isDismissViewNotGone() - && mNotificationStackScroller.isScrolledToBottom() && !mTwoFingerQsExpand; + && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 25a2f93..d286441 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -2206,8 +2206,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Settings are not available in setup if (!mUserSetup) return; - mNotificationPanel.expand(); - mNotificationPanel.openQs(); + mNotificationPanel.expandWithQs(); if (false) postStartTracing(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index bef561e..287cd6f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -87,6 +87,7 @@ import com.android.server.pm.UserManagerService; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.AppTransition; import com.android.server.wm.WindowManagerService; + import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -1854,9 +1855,14 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (ActivityManagerService.this) { if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in " + (SystemClock.uptimeMillis()-start) + "ms"); - mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(), - memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(), - memInfo.getKernelUsedSizeKb(), nativeTotalPss); + final long cachedKb = memInfo.getCachedSizeKb(); + final long freeKb = memInfo.getFreeSizeKb(); + final long zramKb = memInfo.getZramTotalSizeKb(); + final long kernelKb = memInfo.getKernelUsedSizeKb(); + EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024, + kernelKb*1024, nativeTotalPss*1024); + mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, + nativeTotalPss); } } @@ -7056,7 +7062,6 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - final IPackageManager pm = AppGlobals.getPackageManager(); final String authority = uri.getAuthority(); final ProviderInfo pi = getProviderInfoLocked(authority, userId); if (pi == null) { @@ -8167,7 +8172,7 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void resizeStack(int stackId, Rect bounds) { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, - "resizeStackBox()"); + "resizeStack()"); long ident = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -10039,7 +10044,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final boolean translucentChanged = r.changeWindowTranslucency(true); if (translucentChanged) { - r.task.stack.releaseBackgroundResources(); + r.task.stack.releaseBackgroundResources(r); mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); } mWindowManager.setAppFullscreen(token, true); @@ -10066,7 +10071,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final boolean translucentChanged = r.changeWindowTranslucency(false); if (translucentChanged) { - r.task.stack.convertToTranslucent(r); + r.task.stack.convertActivityToTranslucent(r); } mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); mWindowManager.setAppFullscreen(token, false); @@ -13902,9 +13907,14 @@ public final class ActivityManagerService extends ActivityManagerNative memInfo.readMemInfo(); if (nativeProcTotalPss > 0) { synchronized (this) { - mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(), - memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(), - memInfo.getKernelUsedSizeKb(), nativeProcTotalPss); + final long cachedKb = memInfo.getCachedSizeKb(); + final long freeKb = memInfo.getFreeSizeKb(); + final long zramKb = memInfo.getZramTotalSizeKb(); + final long kernelKb = memInfo.getKernelUsedSizeKb(); + EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024, + kernelKb*1024, nativeProcTotalPss*1024); + mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, + nativeProcTotalPss); } } if (!brief) { @@ -15783,14 +15793,14 @@ public final class ActivityManagerService extends ActivityManagerNative callerPackage, callingPid, callingUid, resolvedType, requiredPermission, appOp, receivers, resultTo, resultCode, resultData, map, ordered, sticky, false, userId); + if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r + ": prev had " + queue.mOrderedBroadcasts.size()); - if (DEBUG_BROADCAST) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq); - } - boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); + if (DEBUG_BROADCAST) Slog.i( + TAG, "Enqueueing broadcast " + r.intent.getAction()); + + boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { queue.enqueueOrderedBroadcastLocked(r); queue.scheduleBroadcastsLocked(); @@ -16121,6 +16131,15 @@ public final class ActivityManagerService extends ActivityManagerNative return mStackSupervisor.getFocusedStack(); } + @Override + public int getFocusedStackId() throws RemoteException { + ActivityStack focusedStack = getFocusedStack(); + if (focusedStack != null) { + return focusedStack.getStackId(); + } + return -1; + } + public Configuration getConfiguration() { Configuration ci; synchronized(this) { @@ -17098,6 +17117,7 @@ public final class ActivityManagerService extends ActivityManagerNative * Record new PSS sample for a process. */ void recordPssSample(ProcessRecord proc, int procState, long pss, long uss, long now) { + EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss*1024, uss*1024); proc.lastPssTime = now; proc.baseProcessTracker.addPss(pss, uss, true, proc.pkgList); if (DEBUG_PSS) Slog.d(TAG, "PSS of " + proc.toShortString() @@ -18351,8 +18371,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private Set getProfileIdsLocked(int userId) { - Set userIds = new HashSet<Integer>(); + private Set<Integer> getProfileIdsLocked(int userId) { + Set<Integer> userIds = new HashSet<Integer>(); final List<UserInfo> profiles = getUserManagerLocked().getProfiles( userId, false /* enabledOnly */); for (UserInfo user : profiles) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index dbd787b..91013ef 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -220,6 +220,9 @@ final class ActivityStack { */ boolean mConfigWillChange; + // Whether or not this stack covers the entire screen; by default stacks are full screen + boolean mFullscreen = true; + long mLaunchStartTime = 0; long mFullyDrawnStartTime = 0; @@ -1125,7 +1128,8 @@ final class ActivityStack { final int numStacks = mStacks.size(); while (stackNdx < numStacks) { - tasks = mStacks.get(stackNdx).mTaskHistory; + ActivityStack historyStack = mStacks.get(stackNdx); + tasks = historyStack.mTaskHistory; final int numTasks = tasks.size(); while (taskNdx < numTasks) { activities = tasks.get(taskNdx).mActivities; @@ -1133,7 +1137,7 @@ final class ActivityStack { while (activityNdx < numActivities) { final ActivityRecord activity = activities.get(activityNdx); if (!activity.finishing) { - return activity.fullscreen ? null : activity; + return historyStack.mFullscreen && activity.fullscreen ? null : activity; } ++activityNdx; } @@ -1149,7 +1153,7 @@ final class ActivityStack { // Checks if any of the stacks above this one has a fullscreen activity behind it. // If so, this stack is hidden, otherwise it is visible. - private boolean isStackVisible() { + private boolean isStackVisibleLocked() { if (!isAttached()) { return false; } @@ -1164,11 +1168,18 @@ final class ActivityStack { * wallpaper to be shown behind it. */ for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) { - final ArrayList<TaskRecord> tasks = mStacks.get(i).getAllTasks(); - for (int taskNdx = 0; taskNdx < tasks.size(); taskNdx++) { + ActivityStack stack = mStacks.get(i); + // stack above isn't full screen, so, we assume we're still visible. at some point + // we should look at the stack bounds to see if we're occluded even if the stack + // isn't fullscreen + if (!stack.mFullscreen) { + continue; + } + final ArrayList<TaskRecord> tasks = stack.getAllTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = tasks.get(taskNdx); final ArrayList<ActivityRecord> activities = task.mActivities; - for (int activityNdx = 0; activityNdx < activities.size(); activityNdx++) { + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = activities.get(activityNdx); // Conditions for an activity to obscure the stack we're @@ -1214,7 +1225,7 @@ final class ActivityStack { // If the top activity is not fullscreen, then we need to // make sure any activities under it are now visible. boolean aboveTop = true; - boolean behindFullscreen = !isStackVisible(); + boolean behindFullscreen = !isStackVisibleLocked(); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); @@ -1337,7 +1348,7 @@ final class ActivityStack { // This case created for transitioning activities from // translucent to opaque {@link Activity#convertToOpaque}. if (getVisibleBehindActivity() == r) { - releaseBackgroundResources(); + releaseBackgroundResources(r); } else { if (!mStackSupervisor.mStoppingActivities.contains(r)) { mStackSupervisor.mStoppingActivities.add(r); @@ -1369,7 +1380,7 @@ final class ActivityStack { } } - void convertToTranslucent(ActivityRecord r) { + void convertActivityToTranslucent(ActivityRecord r) { mTranslucentActivityWaiting = r; mUndrawnActivitiesBelowTopTranslucent.clear(); mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT); @@ -3282,10 +3293,9 @@ final class ActivityStack { } } - void releaseBackgroundResources() { + void releaseBackgroundResources(ActivityRecord r) { if (hasVisibleBehindActivity() && !mHandler.hasMessages(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG)) { - final ActivityRecord r = getVisibleBehindActivity(); if (r == topRunningActivityLocked(null)) { // Don't release the top activity if it has requested to run behind the next // activity. @@ -4143,6 +4153,10 @@ final class ActivityStack { boolean updateOverrideConfiguration(Configuration newConfig) { Configuration oldConfig = mOverrideConfig; mOverrideConfig = (newConfig == null) ? Configuration.EMPTY : newConfig; + // we override the configuration only when the stack's dimensions are different from + // the display. in this manner, we know that if the override configuration is empty, + // the stack is necessarily full screen + mFullscreen = Configuration.EMPTY.equals(mOverrideConfig); return !mOverrideConfig.equals(oldConfig); } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 4674cc2..b4455b6 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3752,6 +3752,13 @@ public final class ActivityStackSupervisor implements DisplayListener { } @Override + public int getStackId() { + synchronized (mService) { + return mStackId; + } + } + + @Override public boolean injectEvent(InputEvent event) { final long origId = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 9b7d0b2..7ab3794 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -513,11 +513,7 @@ public final class BroadcastQueue { } } try { - if (DEBUG_BROADCAST_LIGHT) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Delivering to " + filter - + " (seq=" + seq + "): " + r); - } + if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG, "Delivering to " + filter + " : " + r); performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, r.userId); @@ -662,12 +658,9 @@ public final class BroadcastQueue { // result if requested... if (r.resultTo != null) { try { - if (DEBUG_BROADCAST) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Finishing broadcast [" - + mQueueName + "] " + r.intent.getAction() - + " seq=" + seq + " app=" + r.callerApp); - } + if (DEBUG_BROADCAST) Slog.i(TAG, + "Finishing broadcast [" + mQueueName + "] " + + r.intent.getAction() + " app=" + r.callerApp); performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, false, false, r.userId); diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index e43e717..9a645df 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -98,3 +98,8 @@ option java_package com.android.server.am # Running pre boot receiver 30045 am_pre_boot (User|1|5),(Package|3) + +# Report collection of global memory state +30046 am_meminfo (CachedKb|2|2),(FreeKb|2|2),(ZramKb|2|2),(KernelKb|2|2),(NativeKb|2|2) +# Report collection of memory used by a process +30047 am_pss (Pid|1|5),(UID|1|5),(Process Name|3),(PssKb|2|2),(UssKb|2|2) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index ce52920..a8f6954 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -55,10 +55,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( mAddress, mService.getVendorId())); startQueuedActions(); - - // Switch TV input after bootup. - setActiveSource(true); - maySendActiveSource(Constants.ADDR_TV); } @Override diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index da63caa..df31158 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -250,77 +250,37 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } - boolean isMute = direction == MediaSessionManager.DIRECTION_MUTE; - if (direction > 1) { - direction = 1; - } else if (direction < -1) { - direction = -1; - } if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { if (mUseMasterVolume) { // If this device only uses master volume and playback is local // just adjust the master volume and return. - boolean isMasterMute = mAudioManager.isMasterMute(); - if (isMute) { - mAudioManagerInternal.setMasterMuteForUid(!isMasterMute, - flags, packageName, mService.mICallback, uid); - } else { - mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName, - uid); - if (isMasterMute) { - mAudioManagerInternal.setMasterMuteForUid(false, - flags, packageName, mService.mICallback, uid); - } - } + mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName, + uid); return; } int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); - boolean isStreamMute = mAudioManager.isStreamMute(stream); if (useSuggested) { if (AudioSystem.isStreamActive(stream, 0)) { - if (isMute) { - mAudioManager.setStreamMute(stream, !isStreamMute); - } else { - mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction, - flags, packageName, uid); - if (isStreamMute && direction != 0) { - mAudioManager.setStreamMute(stream, false); - } - } + mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction, + flags, packageName, uid); } else { flags |= previousFlagPlaySound; - isStreamMute = - mAudioManager.isStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE); - if (isMute) { - mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE, - !isStreamMute); - } else { - mAudioManagerInternal.adjustSuggestedStreamVolumeForUid( - AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName, - uid); - if (isStreamMute && direction != 0) { - mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE, - false); - } - } + mAudioManagerInternal.adjustSuggestedStreamVolumeForUid( + AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName, + uid); } } else { - if (isMute) { - mAudioManager.setStreamMute(stream, !isStreamMute); - } else { - mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, - packageName, uid); - if (isStreamMute && direction != 0) { - mAudioManager.setStreamMute(stream, false); - } - } + mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, + packageName, uid); } } else { if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) { // Nothing to do, the volume cannot be changed return; } - if (isMute) { + if (direction == AudioManager.ADJUST_TOGGLE_MUTE + || direction == AudioManager.ADJUST_MUTE + || direction == AudioManager.ADJUST_UNMUTE) { Log.w(TAG, "Muting remote playback is not supported"); return; } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 667d02a..0500f94 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -872,30 +872,10 @@ public class MediaSessionService extends SystemService implements Monitor { try { String packageName = getContext().getOpPackageName(); if (mUseMasterVolume) { - boolean isMasterMute = mAudioService.isMasterMute(); - if (direction == MediaSessionManager.DIRECTION_MUTE) { - mAudioService.setMasterMute(!isMasterMute, flags, packageName, mICallback); - } else { mAudioService.adjustMasterVolume(direction, flags, packageName); - // Do not call setMasterMute when direction = 0 which is used just to - // show the UI. - if (isMasterMute && direction != 0) { - mAudioService.setMasterMute(false, flags, packageName, mICallback); - } - } } else { - boolean isStreamMute = mAudioService.isStreamMute(suggestedStream); - if (direction == MediaSessionManager.DIRECTION_MUTE) { - mAudioManager.setStreamMute(suggestedStream, !isStreamMute); - } else { - mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, - flags, packageName); - // Do not call setStreamMute when direction = 0 which is used just to - // show the UI. - if (isStreamMute && direction != 0) { - mAudioManager.setStreamMute(suggestedStream, false); - } - } + mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, + flags, packageName); } } catch (RemoteException e) { Log.e(TAG, "Error adjusting default volume.", e); diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 5d73149..6660843 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -26,6 +26,7 @@ import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Slog; import android.util.TypedValue; + import com.android.server.EventLogTags; import java.io.PrintWriter; @@ -126,6 +127,7 @@ public class TaskStack { boolean oldFullscreen = mFullscreen; if (mDisplayContent != null) { mDisplayContent.getLogicalDisplayRect(mTmpRect); + bounds.intersect(mTmpRect); // ensure bounds are entirely within the display rect mFullscreen = mTmpRect.equals(bounds); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index e4d0b77..bec0f66 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1914,12 +1914,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordQuality(ComponentName who, int quality, int userHandle) { + public void setPasswordQuality(ComponentName who, int quality) { if (!mHasFeature) { return; } + final int userHandle = UserHandle.getCallingUserId(); validateQualityConstant(quality); - enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1963,11 +1963,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumLength(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumLength(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2010,11 +2010,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordHistoryLength(ComponentName who, int length, int userHandle) { + public void setPasswordHistoryLength(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2057,11 +2057,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordExpirationTimeout(ComponentName who, long timeout, int userHandle) { + public void setPasswordExpirationTimeout(ComponentName who, long timeout) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2226,11 +2226,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumUpperCase(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumUpperCase(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2273,8 +2273,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumLowerCase(ComponentName who, int length, int userHandle) { - enforceCrossUserPermission(userHandle); + public void setPasswordMinimumLowerCase(ComponentName who, int length) { + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2317,11 +2317,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumLetters(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumLetters(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2364,11 +2364,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumNumeric(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumNumeric(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2411,11 +2411,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumSymbols(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumSymbols(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2458,11 +2458,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumNonLetter(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumNonLetter(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2522,8 +2522,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked(null, - DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (policy.mActivePasswordQuality < getPasswordQuality(null, userHandle) || policy.mActivePasswordLength < getPasswordMinimumLength(null, userHandle)) { return false; @@ -2556,11 +2555,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, int userHandle) { + public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -2632,11 +2631,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return strictestAdmin; } - public boolean resetPassword(String passwordOrNull, int flags, int userHandle) { + public boolean resetPassword(String passwordOrNull, int flags) { if (!mHasFeature) { return false; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); enforceNotManagedProfile(userHandle, "reset the password"); String password = passwordOrNull != null ? passwordOrNull : ""; @@ -2767,11 +2766,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return true; } - public void setMaximumTimeToLock(ComponentName who, long timeMs, int userHandle) { + public void setMaximumTimeToLock(ComponentName who, long timeMs) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -3231,11 +3230,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public ComponentName setGlobalProxy(ComponentName who, String proxySpec, - String exclusionList, int userHandle) { + String exclusionList) { if (!mHasFeature) { return null; } - enforceCrossUserPermission(userHandle); synchronized(this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -3261,7 +3259,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // If the user is not the owner, don't set the global proxy. Fail silently. if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) { Slog.w(LOG_TAG, "Only the owner is allowed to set the global proxy. User " - + userHandle + " is not permitted."); + + UserHandle.getCallingUserId() + " is not permitted."); return null; } if (proxySpec == null) { @@ -3371,11 +3369,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * Set the storage encryption request for a single admin. Returns the new total request * status (for all admins). */ - public int setStorageEncryption(ComponentName who, boolean encrypt, int userHandle) { + public int setStorageEncryption(ComponentName who, boolean encrypt) { if (!mHasFeature) { return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { // Check for permissions if (who == null) { @@ -3507,11 +3505,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Set whether the screen capture is disabled for the user managed by the specified admin. */ - public void setScreenCaptureDisabled(ComponentName who, int userHandle, boolean disabled) { + public void setScreenCaptureDisabled(ComponentName who, boolean disabled) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -3566,11 +3564,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Set whether auto time is required by the specified admin (must be device owner). */ - public void setAutoTimeRequired(ComponentName who, int userHandle, boolean required) { + public void setAutoTimeRequired(ComponentName who, boolean required) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -3617,11 +3615,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Disables all device cameras according to the specified admin. */ - public void setCameraDisabled(ComponentName who, boolean disabled, int userHandle) { + public void setCameraDisabled(ComponentName who, boolean disabled) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -3666,11 +3664,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Selectively disable keyguard features. */ - public void setKeyguardDisabledFeatures(ComponentName who, int which, int userHandle) { + public void setKeyguardDisabledFeatures(ComponentName who, int which) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); enforceNotManagedProfile(userHandle, "disable keyguard features"); synchronized (this) { if (who == null) { @@ -3920,7 +3918,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Bundle userRestrictions = mUserManager.getUserRestrictions(); mUserManager.setUserRestrictions(new Bundle(), userHandle); if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) { - audioManager.setMasterMute(false); + audioManager.adjustMasterVolume(AudioManager.ADJUST_UNMUTE, 0); } if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) { audioManager.setMicrophoneMute(false); @@ -4216,11 +4214,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, - PersistableBundle args, int userHandle) { + PersistableBundle args) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); enforceNotManagedProfile(userHandle, "set trust agent configuration"); synchronized (this) { if (admin == null) { @@ -4841,7 +4839,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { iAudioService.setMicrophoneMute(true, who.getPackageName()); } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - iAudioService.setMasterMute(true, 0, who.getPackageName(), null); + iAudioService.adjustMasterVolume(AudioManager.ADJUST_MUTE, 0, + who.getPackageName()); } } catch (RemoteException re) { Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); @@ -4906,7 +4905,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { iAudioService.setMicrophoneMute(false, who.getPackageName()); } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - iAudioService.setMasterMute(false, 0, who.getPackageName(), null); + iAudioService.adjustMasterVolume(AudioManager.ADJUST_UNMUTE, 0, + who.getPackageName()); } } catch (RemoteException re) { Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); @@ -5361,8 +5361,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setMasterVolumeMuted(ComponentName who, boolean on) { - final ContentResolver contentResolver = mContext.getContentResolver(); - synchronized (this) { if (who == null) { throw new NullPointerException("ComponentName is null"); @@ -5372,7 +5370,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { IAudioService iAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); try{ - iAudioService.setMasterMute(on, 0, who.getPackageName(), null); + iAudioService.adjustMasterVolume( + on ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, 0, + who.getPackageName()); } catch (RemoteException re) { Slog.e(LOG_TAG, "Failed to setMasterMute", re); } diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index a59505c..07476e3 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -39,8 +39,6 @@ import android.text.style.TtsSpan; import android.util.SparseIntArray; import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY; -import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY; -import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY; import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING; import java.util.Locale; @@ -2200,8 +2198,8 @@ public class PhoneNumberUtils if (!TextUtils.isEmpty(dialStr)) { if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) { - String currIso = SystemProperties.get(PROPERTY_OPERATOR_ISO_COUNTRY, ""); - String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); + String currIso = TelephonyManager.getDefault().getNetworkCountryIso(); + String defaultIso = TelephonyManager.getDefault().getSimCountryIso(); if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) { return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, getFormatTypeFromCountryCode(currIso), @@ -2223,7 +2221,7 @@ public class PhoneNumberUtils public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) { if (!TextUtils.isEmpty(dialStr)) { if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) { - String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); + String defaultIso = TelephonyManager.getDefault().getSimCountryIso(); if (!TextUtils.isEmpty(defaultIso)) { int format = getFormatTypeFromCountryCode(defaultIso); return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index c67629d..aca94e9 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1101,9 +1101,7 @@ public class SubscriptionManager { // What else can we do? return false; } - // FIXME: use better way to get roaming status instead of reading from system property - return Boolean.parseBoolean(TelephonyManager.getTelephonyProperty(phoneId, - TelephonyProperties.PROPERTY_OPERATOR_ISROAMING, null)); + return TelephonyManager.getDefault().isNetworkRoaming(subId); } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 339fc6d..ba5a679 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1077,7 +1077,7 @@ public class TelephonyManager { * on a CDMA network). */ public String getNetworkOperator() { - return getNetworkOperator(getDefaultSubscription()); + return getNetworkOperatorForPhone(getDefaultPhone()); } /** @@ -1091,8 +1091,23 @@ public class TelephonyManager { * @param subId */ /** {@hide} */ - public String getNetworkOperator(int subId) { + public String getNetworkOperatorForSubscription(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); + return getNetworkOperatorForPhone(phoneId); + } + + /** + * Returns the numeric name (MCC+MNC) of current registered operator + * for a particular subscription. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + * + * @param phoneId + * @hide + **/ + public String getNetworkOperatorForPhone(int phoneId) { return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, ""); } @@ -1130,7 +1145,7 @@ public class TelephonyManager { * on a CDMA network). */ public String getNetworkCountryIso() { - return getNetworkCountryIso(getDefaultSubscription()); + return getNetworkCountryIsoForPhone(getDefaultPhone()); } /** @@ -1144,8 +1159,23 @@ public class TelephonyManager { * @param subId for which Network CountryIso is returned */ /** {@hide} */ - public String getNetworkCountryIso(int subId) { + public String getNetworkCountryIsoForSubscription(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); + return getNetworkCountryIsoForPhone(phoneId); + } + + /** + * Returns the ISO country code equivalent of the current registered + * operator's MCC (Mobile Country Code) of a subscription. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + * + * @param phoneId for which Network CountryIso is returned + */ + /** {@hide} */ + public String getNetworkCountryIsoForPhone(int phoneId) { return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, ""); } @@ -1537,6 +1567,34 @@ public class TelephonyManager { * @see #getSimState */ public String getSimOperator() { + return getSimOperatorNumeric(); + } + + /** + * Returns the MCC+MNC (mobile country code + mobile network code) of the + * provider of the SIM. 5 or 6 decimal digits. + * <p> + * Availability: SIM state must be {@link #SIM_STATE_READY} + * + * @see #getSimState + * + * @param subId for which SimOperator is returned + * @hide + */ + public String getSimOperator(int subId) { + return getSimOperatorNumericForSubscription(subId); + } + + /** + * Returns the MCC+MNC (mobile country code + mobile network code) of the + * provider of the SIM. 5 or 6 decimal digits. + * <p> + * Availability: SIM state must be {@link #SIM_STATE_READY} + * + * @see #getSimState + * @hide + */ + public String getSimOperatorNumeric() { int subId = SubscriptionManager.getDefaultDataSubId(); if (!SubscriptionManager.isUsableSubIdValue(subId)) { subId = SubscriptionManager.getDefaultSmsSubId(); @@ -1547,8 +1605,8 @@ public class TelephonyManager { } } } - Rlog.d(TAG, "getSimOperator(): default subId=" + subId); - return getSimOperator(subId); + Rlog.d(TAG, "getSimOperatorNumeric(): default subId=" + subId); + return getSimOperatorNumericForSubscription(subId); } /** @@ -1560,14 +1618,24 @@ public class TelephonyManager { * @see #getSimState * * @param subId for which SimOperator is returned + * @hide */ - /** {@hide} */ - public String getSimOperator(int subId) { + public String getSimOperatorNumericForSubscription(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); - String operator = getTelephonyProperty(phoneId, + return getSimOperatorNumericForPhone(phoneId); + } + + /** + * Returns the MCC+MNC (mobile country code + mobile network code) of the + * provider of the SIM for a particular subscription. 5 or 6 decimal digits. + * <p> + * + * @param phoneId for which SimOperator is returned + * @hide + */ + public String getSimOperatorNumericForPhone(int phoneId) { + return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, ""); - Rlog.d(TAG, "getSimOperator: subId=" + subId + " operator=" + operator); - return operator; } /** @@ -1578,7 +1646,7 @@ public class TelephonyManager { * @see #getSimState */ public String getSimOperatorName() { - return getSimOperatorName(getDefaultSubscription()); + return getSimOperatorNameForPhone(getDefaultPhone()); } /** @@ -1589,30 +1657,61 @@ public class TelephonyManager { * @see #getSimState * * @param subId for which SimOperatorName is returned + * @hide */ - /** {@hide} */ - public String getSimOperatorName(int subId) { + public String getSimOperatorNameForSubscription(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); - return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, ""); + return getSimOperatorNameForPhone(phoneId); + } + + /** + * Returns the Service Provider Name (SPN). + * + * @hide + */ + public String getSimOperatorNameForPhone(int phoneId) { + return getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, ""); } /** * Returns the ISO country code equivalent for the SIM provider's country code. */ public String getSimCountryIso() { - return getSimCountryIso(getDefaultSubscription()); + return getSimCountryIsoForPhone(getDefaultPhone()); } /** * Returns the ISO country code equivalent for the SIM provider's country code. * * @param subId for which SimCountryIso is returned + * + * @hide */ - /** {@hide} */ public String getSimCountryIso(int subId) { + return getSimCountryIsoForSubscription(subId); + } + + /** + * Returns the ISO country code equivalent for the SIM provider's country code. + * + * @param subId for which SimCountryIso is returned + * + * @hide + */ + public String getSimCountryIsoForSubscription(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); - return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY, - ""); + return getSimCountryIsoForPhone(phoneId); + } + + /** + * Returns the ISO country code equivalent for the SIM provider's country code. + * + * @hide + */ + public String getSimCountryIsoForPhone(int phoneId) { + return getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); } /** @@ -3677,4 +3776,344 @@ public class TelephonyManager { return false; } } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC for the default phone. + * + * @hide + */ + public void setSimOperatorNumeric(String numeric) { + int phoneId = getDefaultPhone(); + setSimOperatorNumericForPhone(phoneId, numeric); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC for the given phone. + * + * @hide + */ + public void setSimOperatorNumericForPhone(int phoneId, String numeric) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, numeric); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC for the default phone. + * + * @hide + */ + public void setSimOperatorName(String name) { + int phoneId = getDefaultPhone(); + setSimOperatorNameForPhone(phoneId, name); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC for the given phone. + * + * @hide + */ + public void setSimOperatorNameForPhone(int phoneId, String name) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, name); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY for the default phone. + * + * @hide + */ + public void setSimCountryIso(String iso) { + int phoneId = getDefaultPhone(); + setSimCountryIsoForPhone(phoneId, iso); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY for the given phone. + * + * @hide + */ + public void setSimCountryIsoForPhone(int phoneId, String iso) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY, iso); + } + + /** + * Set TelephonyProperties.PROPERTY_SIM_STATE for the default phone. + * + * @hide + */ + public void setSimState(String state) { + int phoneId = getDefaultPhone(); + setSimStateForPhone(phoneId, state); + } + + /** + * Set TelephonyProperties.PROPERTY_SIM_STATE for the given phone. + * + * @hide + */ + public void setSimStateForPhone(int phoneId, String state) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_SIM_STATE, state); + } + + /** + * Set baseband version for the default phone. + * + * @param version baseband version + * @hide + */ + public void setBasebandVersion(String version) { + int phoneId = getDefaultPhone(); + setBasebandVersionForPhone(phoneId, version); + } + + /** + * Set baseband version by phone id. + * + * @param phoneId for which baseband version is set + * @param version baseband version + * @hide + */ + public void setBasebandVersionForPhone(int phoneId, String version) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + String prop = TelephonyProperties.PROPERTY_BASEBAND_VERSION + + ((phoneId == 0) ? "" : Integer.toString(phoneId)); + SystemProperties.set(prop, version); + } + } + + /** + * Set phone type for the default phone. + * + * @param type phone type + * + * @hide + */ + public void setPhoneType(int type) { + int phoneId = getDefaultPhone(); + setPhoneType(phoneId, type); + } + + /** + * Set phone type by phone id. + * + * @param phoneId for which phone type is set + * @param type phone type + * + * @hide + */ + public void setPhoneType(int phoneId, int type) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + TelephonyManager.setTelephonyProperty(phoneId, + TelephonyProperties.CURRENT_ACTIVE_PHONE, String.valueOf(type)); + } + } + + /** + * Get OTASP number schema for the default phone. + * + * @param defaultValue default value + * @return OTA SP number schema + * + * @hide + */ + public String getOtaSpNumberSchema(String defaultValue) { + int phoneId = getDefaultPhone(); + return getOtaSpNumberSchemaForPhone(phoneId, defaultValue); + } + + /** + * Get OTASP number schema by phone id. + * + * @param phoneId for which OTA SP number schema is get + * @param defaultValue default value + * @return OTA SP number schema + * + * @hide + */ + public String getOtaSpNumberSchemaForPhone(int phoneId, String defaultValue) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + return TelephonyManager.getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_OTASP_NUM_SCHEMA, defaultValue); + } + + return defaultValue; + } + + /** + * Get SMS receive capable from system property for the default phone. + * + * @param defaultValue default value + * @return SMS receive capable + * + * @hide + */ + public boolean getSmsReceiveCapable(boolean defaultValue) { + int phoneId = getDefaultPhone(); + return getSmsReceiveCapableForPhone(phoneId, defaultValue); + } + + /** + * Get SMS receive capable from system property by phone id. + * + * @param phoneId for which SMS receive capable is get + * @param defaultValue default value + * @return SMS receive capable + * + * @hide + */ + public boolean getSmsReceiveCapableForPhone(int phoneId, boolean defaultValue) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + return Boolean.valueOf(TelephonyManager.getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_SMS_RECEIVE, String.valueOf(defaultValue))); + } + + return defaultValue; + } + + /** + * Get SMS send capable from system property for the default phone. + * + * @param defaultValue default value + * @return SMS send capable + * + * @hide + */ + public boolean getSmsSendCapable(boolean defaultValue) { + int phoneId = getDefaultPhone(); + return getSmsSendCapableForPhone(phoneId, defaultValue); + } + + /** + * Get SMS send capable from system property by phone id. + * + * @param phoneId for which SMS send capable is get + * @param defaultValue default value + * @return SMS send capable + * + * @hide + */ + public boolean getSmsSendCapableForPhone(int phoneId, boolean defaultValue) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + return Boolean.valueOf(TelephonyManager.getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_SMS_SEND, String.valueOf(defaultValue))); + } + + return defaultValue; + } + + /** + * Set the alphabetic name of current registered operator. + * @param name the alphabetic name of current registered operator. + * @hide + */ + public void setNetworkOperatorName(String name) { + int phoneId = getDefaultPhone(); + setNetworkOperatorNameForPhone(phoneId, name); + } + + /** + * Set the alphabetic name of current registered operator. + * @param phoneId which phone you want to set + * @param name the alphabetic name of current registered operator. + * @hide + */ + public void setNetworkOperatorNameForPhone(int phoneId, String name) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ALPHA, name); + } + } + + /** + * Set the numeric name (MCC+MNC) of current registered operator. + * @param operator the numeric name (MCC+MNC) of current registered operator + * @hide + */ + public void setNetworkOperatorNumeric(String numeric) { + int phoneId = getDefaultPhone(); + setNetworkOperatorNumericForPhone(phoneId, numeric); + } + + /** + * Set the numeric name (MCC+MNC) of current registered operator. + * @param phoneId for which phone type is set + * @param operator the numeric name (MCC+MNC) of current registered operator + * @hide + */ + public void setNetworkOperatorNumericForPhone(int phoneId, String numeric) { + setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, numeric); + } + + /** + * Set roaming state of the current network, for GSM purposes. + * @param isRoaming is network in romaing state or not + * @hide + */ + public void setNetworkRoaming(boolean isRoaming) { + int phoneId = getDefaultPhone(); + setNetworkRoamingForPhone(phoneId, isRoaming); + } + + /** + * Set roaming state of the current network, for GSM purposes. + * @param phoneId which phone you want to set + * @param isRoaming is network in romaing state or not + * @hide + */ + public void setNetworkRoamingForPhone(int phoneId, boolean isRoaming) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ISROAMING, + isRoaming ? "true" : "false"); + } + } + + /** + * Set the ISO country code equivalent of the current registered + * operator's MCC (Mobile Country Code). + * @param iso the ISO country code equivalent of the current registered + * @hide + */ + public void setNetworkCountryIso(String iso) { + int phoneId = getDefaultPhone(); + setNetworkCountryIsoForPhone(phoneId, iso); + } + + /** + * Set the ISO country code equivalent of the current registered + * operator's MCC (Mobile Country Code). + * @param phoneId which phone you want to set + * @param iso the ISO country code equivalent of the current registered + * @hide + */ + public void setNetworkCountryIsoForPhone(int phoneId, String iso) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, iso); + } + } + + /** + * Set the network type currently in use on the device for data transmission. + * @param type the network type currently in use on the device for data transmission + * @hide + */ + public void setDataNetworkType(int type) { + int phoneId = getDefaultPhone(); + setDataNetworkTypeForPhone(phoneId, type); + } + + /** + * Set the network type currently in use on the device for data transmission. + * @param phoneId which phone you want to set + * @param type the network type currently in use on the device for data transmission + * @hide + */ + public void setDataNetworkTypeForPhone(int phoneId, int type) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE, + ServiceState.rilRadioTechnologyToString(type)); + } + } } |