Merge "Update the page count in the print UI to meet new UX." into lmp-dev
diff --git a/Android.mk b/Android.mk
index 96b2b17..e2fa7d1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -118,9 +118,7 @@
core/java/android/content/IIntentReceiver.aidl \
core/java/android/content/IIntentSender.aidl \
core/java/android/content/IOnPrimaryClipChangedListener.aidl \
- core/java/android/content/IPermissionResponseCallback.aidl \
core/java/android/content/IRestrictionsManager.aidl \
- core/java/android/content/IRestrictionsProvider.aidl \
core/java/android/content/ISyncAdapter.aidl \
core/java/android/content/ISyncContext.aidl \
core/java/android/content/ISyncServiceAdapter.aidl \
@@ -336,12 +334,11 @@
media/java/android/media/tv/ITvInputHardware.aidl \
media/java/android/media/tv/ITvInputHardwareCallback.aidl \
media/java/android/media/tv/ITvInputManager.aidl \
+ media/java/android/media/tv/ITvInputManagerCallback.aidl \
media/java/android/media/tv/ITvInputService.aidl \
media/java/android/media/tv/ITvInputServiceCallback.aidl \
media/java/android/media/tv/ITvInputSession.aidl \
media/java/android/media/tv/ITvInputSessionCallback.aidl \
- telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl \
- telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl \
telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl \
telecomm/java/com/android/internal/telecomm/ICallVideoClient.aidl \
telecomm/java/com/android/internal/telecomm/IConnectionService.aidl \
@@ -362,10 +359,6 @@
telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
telephony/java/com/android/internal/telephony/ITelephony.aidl \
- telephony/java/com/android/internal/telephony/IThirdPartyCallListener.aidl \
- telephony/java/com/android/internal/telephony/IThirdPartyCallProvider.aidl \
- telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl \
- telephony/java/com/android/internal/telephony/IThirdPartyCallService.aidl \
telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
telephony/java/com/android/internal/telephony/ISms.aidl \
telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index 58dd2bc..d00050a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -359,6 +359,7 @@
field public static final int buttonTint = 16843889; // 0x1010471
field public static final int buttonTintMode = 16843890; // 0x1010472
field public static final int cacheColorHint = 16843009; // 0x1010101
+ field public static final int calendarTextColor = 16843933; // 0x101049d
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
@@ -447,6 +448,14 @@
field public static final int dashWidth = 16843174; // 0x10101a6
field public static final int data = 16842798; // 0x101002e
field public static final int datePickerStyle = 16843612; // 0x101035c
+ field public static final int dateSelectorBackgroundColor = 16843927; // 0x1010497
+ field public static final int dateSelectorDayOfMonthTextAppearance = 16843929; // 0x1010499
+ field public static final int dateSelectorDayOfWeekBackgroundColor = 16843925; // 0x1010495
+ field public static final int dateSelectorDayOfWeekTextAppearance = 16843926; // 0x1010496
+ field public static final int dateSelectorMonthTextAppearance = 16843928; // 0x1010498
+ field public static final int dateSelectorYearListItemTextAppearance = 16843931; // 0x101049b
+ field public static final int dateSelectorYearListSelectedCircleColor = 16843932; // 0x101049c
+ field public static final int dateSelectorYearTextAppearance = 16843930; // 0x101049a
field public static final int dateTextAppearance = 16843593; // 0x1010349
field public static final int debuggable = 16842767; // 0x101000f
field public static final int defaultValue = 16843245; // 0x10101ed
@@ -742,6 +751,8 @@
field public static final int largeScreens = 16843398; // 0x1010286
field public static final int largestWidthLimitDp = 16843622; // 0x1010366
field public static final int launchMode = 16842781; // 0x101001d
+ field public static final int launchTaskBehindBackgroundAnimation = 16843923; // 0x1010493
+ field public static final int launchTaskBehindSourceAnimation = 16843924; // 0x1010494
field public static final int layerType = 16843604; // 0x1010354
field public static final int layout = 16842994; // 0x10100f2
field public static final int layoutAnimation = 16842988; // 0x10100ec
@@ -1368,6 +1379,7 @@
field public static final int windowContentTransitions = 16843794; // 0x1010412
field public static final int windowDisablePreview = 16843298; // 0x1010222
field public static final int windowDrawsSystemBarBackgrounds = 16843858; // 0x1010452
+ field public static final int windowElevation = 16843922; // 0x1010492
field public static final int windowEnableSplitTouch = 16843543; // 0x1010317
field public static final int windowEnterAnimation = 16842932; // 0x10100b4
field public static final int windowEnterTransition = 16843833; // 0x1010439
@@ -3309,6 +3321,7 @@
method public android.view.LayoutInflater getLayoutInflater();
method public android.app.LoaderManager getLoaderManager();
method public java.lang.String getLocalClassName();
+ method public final android.media.session.MediaController getMediaController();
method public android.view.MenuInflater getMenuInflater();
method public final android.app.Activity getParent();
method public android.content.Intent getParentActivityIntent();
@@ -3432,6 +3445,7 @@
method public void setFinishOnTouchOutside(boolean);
method public void setImmersive(boolean);
method public void setIntent(android.content.Intent);
+ method public final void setMediaController(android.media.session.MediaController);
method public boolean setMediaPlaying(boolean);
method public final void setProgress(int);
method public final void setProgressBarIndeterminate(boolean);
@@ -3510,6 +3524,7 @@
method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException;
method public java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException;
+ method public boolean isInLockTaskMode();
method public boolean isLowRamDevice();
method public static boolean isRunningInTestHarness();
method public static boolean isUserAMonkey();
@@ -3660,6 +3675,7 @@
public class ActivityOptions {
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
+ method public static android.app.ActivityOptions makeLaunchTaskBehindAnimation();
method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, java.lang.String);
method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View, java.lang.String>...);
@@ -6571,11 +6587,10 @@
package android.content {
- public abstract class AbstractRestrictionsProvider extends android.app.Service {
+ public abstract class AbstractRestrictionsProvider extends android.content.BroadcastReceiver {
ctor public AbstractRestrictionsProvider();
- method public abstract android.os.Bundle getPermissionResponse(java.lang.String, java.lang.String);
- method public final android.os.IBinder onBind(android.content.Intent);
- method public abstract void requestPermission(java.lang.String, java.lang.String, android.os.Bundle);
+ method public void onReceive(android.content.Context, android.content.Intent);
+ method public abstract void requestPermission(android.content.Context, java.lang.String, java.lang.String, android.os.Bundle);
}
public abstract class AbstractThreadedSyncAdapter {
@@ -7058,6 +7073,7 @@
method public abstract java.io.File getFileStreamPath(java.lang.String);
method public abstract java.io.File getFilesDir();
method public abstract android.os.Looper getMainLooper();
+ method public abstract java.io.File getNoBackupFilesDir();
method public abstract java.io.File getObbDir();
method public abstract java.io.File[] getObbDirs();
method public abstract java.lang.String getPackageCodePath();
@@ -7227,6 +7243,7 @@
method public java.io.File getFileStreamPath(java.lang.String);
method public java.io.File getFilesDir();
method public android.os.Looper getMainLooper();
+ method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
method public java.lang.String getPackageCodePath();
@@ -7690,7 +7707,6 @@
field public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 8388608; // 0x800000
field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000
field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000
- field public static final int FLAG_ACTIVITY_LAUNCH_BEHIND = 4096; // 0x1000
field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000
field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000
field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000
@@ -7957,24 +7973,25 @@
public class RestrictionsManager {
method public android.os.Bundle getApplicationRestrictions();
method public java.util.List<android.content.RestrictionEntry> getManifestRestrictions(java.lang.String);
- method public void getPermissionResponse(java.lang.String, android.content.RestrictionsManager.PermissionResponseCallback);
method public boolean hasRestrictionsProvider();
method public void notifyPermissionResponse(java.lang.String, android.os.Bundle);
method public void requestPermission(java.lang.String, android.os.Bundle);
field public static final java.lang.String ACTION_PERMISSION_RESPONSE_RECEIVED = "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
- field public static final java.lang.String EXTRA_PACKAGE_NAME = "package_name";
- field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "response";
+ field public static final java.lang.String ACTION_REQUEST_PERMISSION = "android.content.action.REQUEST_PERMISSION";
+ field public static final java.lang.String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+ field public static final java.lang.String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE";
+ field public static final java.lang.String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE";
+ field public static final java.lang.String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE";
field public static final java.lang.String REQUEST_KEY_APPROVE_LABEL = "android.request.approve_label";
field public static final java.lang.String REQUEST_KEY_DATA = "android.request.data";
field public static final java.lang.String REQUEST_KEY_DENY_LABEL = "android.request.deny_label";
- field public static final java.lang.String REQUEST_KEY_DEVICE_NAME = "android.request.device";
field public static final java.lang.String REQUEST_KEY_ICON = "android.request.icon";
field public static final java.lang.String REQUEST_KEY_ID = "android.request.id";
field public static final java.lang.String REQUEST_KEY_MESSAGE = "android.request.mesg";
- field public static final java.lang.String REQUEST_KEY_REQUESTOR_NAME = "android.request.requestor";
+ field public static final java.lang.String REQUEST_KEY_NEW_REQUEST = "android.request.new_request";
field public static final java.lang.String REQUEST_KEY_TITLE = "android.request.title";
+ field public static final java.lang.String REQUEST_TYPE_APPROVAL = "android.request.type.approval";
field public static final java.lang.String REQUEST_TYPE_LOCAL_APPROVAL = "android.request.type.local_approval";
- field public static final java.lang.String REQUEST_TYPE_QUESTION = "android.request.type.question";
field public static final java.lang.String RESPONSE_KEY_ERROR_CODE = "android.response.errorcode";
field public static final java.lang.String RESPONSE_KEY_ERROR_MESSAGE = "android.response.errormsg";
field public static final java.lang.String RESPONSE_KEY_RESPONSE_TIMESTAMP = "android.response.timestamp";
@@ -7989,11 +8006,6 @@
field public static final int RESULT_UNKNOWN_REQUEST = 4; // 0x4
}
- public static abstract class RestrictionsManager.PermissionResponseCallback {
- ctor public RestrictionsManager.PermissionResponseCallback();
- method public abstract void onResponse(android.os.Bundle);
- }
-
public class SearchRecentSuggestionsProvider extends android.content.ContentProvider {
ctor public SearchRecentSuggestionsProvider();
method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
@@ -8352,6 +8364,36 @@
field public int reqGlEsVersion;
}
+ public class InstallSessionInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.graphics.Bitmap getIcon();
+ method public java.lang.String getInstallerPackageName();
+ method public java.lang.String getPackageName();
+ method public int getProgress();
+ method public int getSessionId();
+ method public java.lang.CharSequence getTitle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public class InstallSessionParams implements android.os.Parcelable {
+ ctor public InstallSessionParams();
+ method public int describeContents();
+ method public void setDeltaSize(long);
+ method public void setIcon(android.graphics.Bitmap);
+ method public void setInstallLocation(int);
+ method public void setModeFullInstall();
+ method public void setModeInheritExisting();
+ method public void setOriginatingUri(android.net.Uri);
+ method public void setPackageName(java.lang.String);
+ method public void setProgressMax(int);
+ method public void setReferrerUri(android.net.Uri);
+ method public void setSignatures(android.content.pm.Signature[]);
+ method public void setTitle(java.lang.CharSequence);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
public class InstrumentationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
ctor public InstrumentationInfo();
ctor public InstrumentationInfo(android.content.pm.InstrumentationInfo);
@@ -8367,6 +8409,9 @@
field public java.lang.String targetPackage;
}
+ public class KeySet {
+ }
+
public class LabeledIntent extends android.content.Intent {
ctor public LabeledIntent(android.content.Intent, java.lang.String, int, int);
ctor public LabeledIntent(android.content.Intent, java.lang.String, java.lang.CharSequence, int);
@@ -8416,6 +8461,9 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final int INSTALL_LOCATION_AUTO = 0; // 0x0
+ field public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1; // 0x1
+ field public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2; // 0x2
field public static final int REQUESTED_PERMISSION_GRANTED = 2; // 0x2
field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1
field public android.content.pm.ActivityInfo[] activities;
@@ -8423,6 +8471,7 @@
field public android.content.pm.ConfigurationInfo[] configPreferences;
field public long firstInstallTime;
field public int[] gids;
+ field public int installLocation;
field public android.content.pm.InstrumentationInfo[] instrumentation;
field public long lastUpdateTime;
field public java.lang.String packageName;
@@ -8436,10 +8485,52 @@
field public java.lang.String sharedUserId;
field public int sharedUserLabel;
field public android.content.pm.Signature[] signatures;
+ field public java.lang.String[] splitNames;
field public int versionCode;
field public java.lang.String versionName;
}
+ public class PackageInstaller {
+ method public int createSession(android.content.pm.InstallSessionParams) throws java.io.IOException;
+ method public java.util.List<android.content.pm.InstallSessionInfo> getActiveSessions();
+ method public android.content.pm.PackageInstaller.Session openSession(int);
+ method public void registerSessionObserver(android.content.pm.PackageInstaller.SessionObserver);
+ method public void uninstall(java.lang.String, android.content.pm.PackageInstaller.UninstallResultCallback);
+ method public void unregisterSessionObserver(android.content.pm.PackageInstaller.SessionObserver);
+ }
+
+ public static abstract class PackageInstaller.CommitResultCallback {
+ ctor public PackageInstaller.CommitResultCallback();
+ method public abstract void onFailure(java.lang.String);
+ method public void onFailureConflict(java.lang.String, java.lang.String);
+ method public void onFailureIncompatible(java.lang.String);
+ method public void onFailureInvalid(java.lang.String);
+ method public void onFailureStorage(java.lang.String);
+ method public abstract void onSuccess();
+ }
+
+ public static class PackageInstaller.Session implements java.io.Closeable {
+ method public void close();
+ method public void commit(android.content.pm.PackageInstaller.CommitResultCallback);
+ method public void destroy();
+ method public void fsync(java.io.OutputStream) throws java.io.IOException;
+ method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
+ method public void setProgress(int);
+ }
+
+ public static abstract class PackageInstaller.SessionObserver {
+ ctor public PackageInstaller.SessionObserver();
+ method public abstract void onCreated(android.content.pm.InstallSessionInfo);
+ method public abstract void onFinalized(int, boolean);
+ method public abstract void onProgress(int, int);
+ }
+
+ public static abstract class PackageInstaller.UninstallResultCallback {
+ ctor public PackageInstaller.UninstallResultCallback();
+ method public abstract void onFailure(java.lang.String);
+ method public abstract void onSuccess();
+ }
+
public class PackageItemInfo {
ctor public PackageItemInfo();
ctor public PackageItemInfo(android.content.pm.PackageItemInfo);
@@ -8502,8 +8593,10 @@
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+ method public abstract android.content.pm.PackageInstaller getInstaller();
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
method public abstract android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public abstract android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public abstract java.lang.String getNameForUid(int);
@@ -8522,12 +8615,15 @@
method public abstract android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public abstract android.content.pm.KeySet getSigningKeySet(java.lang.String);
method public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public abstract java.lang.String[] getSystemSharedLibraryNames();
method public abstract java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract boolean hasSystemFeature(java.lang.String);
method public abstract boolean isSafeMode();
+ method public abstract boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
+ method public abstract boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
method public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
method public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
@@ -8584,12 +8680,14 @@
field public static final java.lang.String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
field public static final java.lang.String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
field public static final java.lang.String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
+ field public static final java.lang.String FEATURE_SENSOR_AMBIENT_TEMPERATURE = "android.hardware.sensor.ambient_temperature";
field public static final java.lang.String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
field public static final java.lang.String FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass";
field public static final java.lang.String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope";
field public static final java.lang.String FEATURE_SENSOR_HEART_RATE = "android.hardware.sensor.heartrate";
field public static final java.lang.String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light";
field public static final java.lang.String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
+ field public static final java.lang.String FEATURE_SENSOR_RELATIVE_HUMIDITY = "android.hardware.sensor.relative_humidity";
field public static final java.lang.String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter";
field public static final java.lang.String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
field public static final java.lang.String FEATURE_SIP = "android.software.sip";
@@ -16217,19 +16315,14 @@
}
public final class PlaybackState implements android.os.Parcelable {
- ctor public PlaybackState();
- ctor public PlaybackState(android.media.session.PlaybackState);
method public int describeContents();
method public long getActions();
method public long getBufferPosition();
method public java.lang.CharSequence getErrorMessage();
- method public float getPlaybackRate();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
method public long getPosition();
method public int getState();
- method public void setActions(long);
- method public void setBufferPosition(long);
- method public void setErrorMessage(java.lang.CharSequence);
- method public void setState(int, long, float);
method public void writeToParcel(android.os.Parcel, int);
field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
field public static final long ACTION_PAUSE = 2L; // 0x2L
@@ -16256,6 +16349,17 @@
field public static final int STATE_STOPPED = 1; // 0x1
}
+ public static final class PlaybackState.Builder {
+ ctor public PlaybackState.Builder();
+ ctor public PlaybackState.Builder(android.media.session.PlaybackState);
+ method public android.media.session.PlaybackState build();
+ method public android.media.session.PlaybackState.Builder setActions(long);
+ method public android.media.session.PlaybackState.Builder setBufferPosition(long);
+ method public android.media.session.PlaybackState.Builder setErrorMessage(java.lang.CharSequence);
+ method public android.media.session.PlaybackState.Builder setState(int, long, float, long);
+ method public android.media.session.PlaybackState.Builder setState(int, long, float);
+ }
+
}
package android.media.tv {
@@ -16404,8 +16508,13 @@
}
public final class TvInputManager {
- method public boolean getAvailability(java.lang.String);
+ method public int getInputState(java.lang.String);
method public java.util.List<android.media.tv.TvInputInfo> getTvInputList();
+ method public void registerListener(android.media.tv.TvInputManager.TvInputListener, android.os.Handler);
+ method public void unregisterListener(android.media.tv.TvInputManager.TvInputListener);
+ field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
+ field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
+ field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3
field public static final int VIDEO_UNAVAILABLE_REASON_TUNE = 1; // 0x1
field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
@@ -16414,7 +16523,7 @@
public static abstract class TvInputManager.TvInputListener {
ctor public TvInputManager.TvInputListener();
- method public void onAvailabilityChanged(java.lang.String, boolean);
+ method public void onInputStateChanged(java.lang.String, int);
}
public abstract class TvInputService extends android.app.Service {
@@ -24905,6 +25014,7 @@
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
+ field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
field public static final java.lang.String ACTION_INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
field public static final java.lang.String ACTION_INPUT_METHOD_SUBTYPE_SETTINGS = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
field public static final java.lang.String ACTION_INTERNAL_STORAGE_SETTINGS = "android.settings.INTERNAL_STORAGE_SETTINGS";
@@ -27835,6 +27945,62 @@
package android.telecomm {
+ public final class Call {
+ method public void addListener(android.telecomm.Call.Listener);
+ method public void answer();
+ method public void conference();
+ method public void disconnect();
+ method public android.telecomm.RemoteCallVideoProvider getCallVideoProvider();
+ method public java.util.List<java.lang.String> getCannedTextResponses();
+ method public java.util.List<android.telecomm.Call> getChildren();
+ method public android.telecomm.Call.Details getDetails();
+ method public android.telecomm.Call getParent();
+ method public java.lang.String getRemainingPostDialSequence();
+ method public int getState();
+ method public void hold();
+ method public void phoneAccountClicked();
+ method public void playDtmfTone(char);
+ method public void postDialContinue(boolean);
+ method public void reject(boolean, java.lang.String);
+ method public void removeListener(android.telecomm.Call.Listener);
+ method public void splitFromConference();
+ method public void stopDtmfTone();
+ method public void swapWithBackgroundCall();
+ method public void unhold();
+ field public static final int STATE_ACTIVE = 4; // 0x4
+ field public static final int STATE_DIALING = 1; // 0x1
+ field public static final int STATE_DISCONNECTED = 7; // 0x7
+ field public static final int STATE_HOLDING = 3; // 0x3
+ field public static final int STATE_NEW = 0; // 0x0
+ field public static final int STATE_RINGING = 2; // 0x2
+ }
+
+ public static class Call.Details {
+ method public android.telecomm.PhoneAccount getAccount();
+ method public java.lang.String getCallerDisplayName();
+ method public int getCallerDisplayNamePresentation();
+ method public int getCapabilities();
+ method public long getConnectTimeMillis();
+ method public int getDisconnectCauseCode();
+ method public java.lang.String getDisconnectCauseMsg();
+ method public android.telecomm.GatewayInfo getGatewayInfo();
+ method public android.net.Uri getHandle();
+ method public int getHandlePresentation();
+ }
+
+ public static abstract class Call.Listener {
+ ctor public Call.Listener();
+ method public void onCallDestroyed(android.telecomm.Call);
+ method public void onCallVideoProviderChanged(android.telecomm.Call, android.telecomm.RemoteCallVideoProvider);
+ method public void onCannedTextResponsesLoaded(android.telecomm.Call, java.util.List<java.lang.String>);
+ method public void onChildrenChanged(android.telecomm.Call, java.util.List<android.telecomm.Call>);
+ method public void onDetailsChanged(android.telecomm.Call, android.telecomm.Call.Details);
+ method public void onParentChanged(android.telecomm.Call, android.telecomm.Call);
+ method public void onPostDial(android.telecomm.Call, java.lang.String);
+ method public void onPostDialWait(android.telecomm.Call, java.lang.String);
+ method public void onStateChanged(android.telecomm.Call, int);
+ }
+
public final class CallAudioState implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
@@ -27878,39 +28044,10 @@
public class CallPropertyPresentation {
ctor public CallPropertyPresentation();
- field public static final int ALLOWED = 0; // 0x0
- field public static final int PAYPHONE = 3; // 0x3
- field public static final int RESTRICTED = 1; // 0x1
- field public static final int UNKNOWN = 2; // 0x2
- }
-
- public final class CallServiceDescriptor implements android.os.Parcelable {
- method public int describeContents();
- method public java.lang.String getConnectionServiceId();
- method public int getNetworkType();
- method public android.content.ComponentName getServiceComponent();
- method public static android.telecomm.CallServiceDescriptor.Builder newBuilder(android.content.Context);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator CREATOR;
- field public static final int FLAG_MOBILE = 4; // 0x4
- field public static final int FLAG_PSTN = 2; // 0x2
- field public static final int FLAG_WIFI = 1; // 0x1
- }
-
- public static class CallServiceDescriptor.Builder {
- method public android.telecomm.CallServiceDescriptor build();
- method public android.telecomm.CallServiceDescriptor.Builder setConnectionService(java.lang.Class<? extends android.telecomm.ConnectionService>);
- method public android.telecomm.CallServiceDescriptor.Builder setNetworkType(int);
- }
-
- public final class CallServiceLookupResponse {
- method public void setCallServiceDescriptors(java.util.List<android.telecomm.CallServiceDescriptor>);
- }
-
- public abstract class CallServiceProvider extends android.app.Service {
- ctor protected CallServiceProvider();
- method public abstract void lookupCallServices(android.telecomm.CallServiceLookupResponse);
- method public android.os.IBinder onBind(android.content.Intent);
+ field public static final int ALLOWED = 1; // 0x1
+ field public static final int PAYPHONE = 4; // 0x4
+ field public static final int RESTRICTED = 2; // 0x2
+ field public static final int UNKNOWN = 3; // 0x3
}
public final class CallState extends java.lang.Enum {
@@ -27921,8 +28058,6 @@
enum_constant public static final android.telecomm.CallState DISCONNECTED;
enum_constant public static final android.telecomm.CallState NEW;
enum_constant public static final android.telecomm.CallState ON_HOLD;
- enum_constant public static final android.telecomm.CallState POST_DIAL;
- enum_constant public static final android.telecomm.CallState POST_DIAL_WAIT;
enum_constant public static final android.telecomm.CallState RINGING;
}
@@ -28032,7 +28167,8 @@
public abstract class ConnectionService extends android.app.Service {
ctor public ConnectionService();
- method public final void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.RemoteConnection>);
+ method public final void createRemoteIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.RemoteConnection>);
+ method public final void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.RemoteConnection>);
method public final java.util.Collection<android.telecomm.Connection> getAllConnections();
method public final void lookupRemoteAccounts(android.net.Uri, android.telecomm.SimpleResponse<android.net.Uri, java.util.List<android.telecomm.PhoneAccount>>);
method public final void maybeRespondToAccountLookup();
@@ -28040,11 +28176,11 @@
method protected void onConnectionAdded(android.telecomm.Connection);
method protected void onConnectionRemoved(android.telecomm.Connection);
method protected void onCreateConferenceConnection(java.lang.String, android.telecomm.Connection, android.telecomm.Response<java.lang.String, android.telecomm.Connection>);
- method protected void onCreateConnections(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.Connection>);
- method protected void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>);
+ method protected void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.Connection>);
+ method protected void onCreateOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.CreateConnectionResponse<android.telecomm.Connection>);
}
- public static abstract interface ConnectionService.OutgoingCallResponse {
+ public static abstract interface ConnectionService.CreateConnectionResponse {
method public abstract void onCancel(android.telecomm.ConnectionRequest);
method public abstract void onFailure(android.telecomm.ConnectionRequest, int, java.lang.String);
method public abstract void onSuccess(android.telecomm.ConnectionRequest, CONNECTION);
@@ -28084,7 +28220,6 @@
method public java.util.List<java.lang.String> getCannedSmsResponses();
method public int getCapabilities();
method public long getConnectTimeMillis();
- method public android.telecomm.CallServiceDescriptor getCurrentCallServiceDescriptor();
method public int getDisconnectCauseCode();
method public java.lang.String getDisconnectCauseMsg();
method public android.telecomm.GatewayInfo getGatewayInfo();
@@ -28093,35 +28228,57 @@
method public java.lang.String getId();
method public android.telecomm.CallState getState();
method public android.telecomm.StatusHints getStatusHints();
+ method public int getVideoState();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
- public abstract class InCallService extends android.app.Service {
+ public abstract class InCallService {
ctor protected InCallService();
- method protected abstract void addCall(android.telecomm.InCallCall);
- method protected abstract void bringToForeground(boolean);
- method protected final android.telecomm.InCallAdapter getAdapter();
- method protected void onAdapterAttached(android.telecomm.InCallAdapter);
- method protected abstract void onAudioStateChanged(android.telecomm.CallAudioState);
- method public final android.os.IBinder onBind(android.content.Intent);
- method protected abstract void setPostDial(java.lang.String, java.lang.String);
- method protected abstract void setPostDialWait(java.lang.String, java.lang.String);
- method protected abstract void updateCall(android.telecomm.InCallCall);
+ method public final android.os.IBinder getBinder();
+ method public android.telecomm.Phone getPhone();
+ method public void onPhoneCreated(android.telecomm.Phone);
+ method public void onPhoneDestroyed(android.telecomm.Phone);
}
- public final class PhoneAccount implements android.os.Parcelable {
- ctor public PhoneAccount(android.content.ComponentName, java.lang.String, android.net.Uri, java.lang.String, java.lang.String, boolean, boolean);
+ public final class Phone {
+ method public final void addListener(android.telecomm.Phone.Listener);
+ method public final android.telecomm.CallAudioState getAudioState();
+ method public final java.util.List<android.telecomm.Call> getCalls();
+ method public final void removeListener(android.telecomm.Phone.Listener);
+ method public final void setAudioRoute(int);
+ method public final void setMuted(boolean);
+ }
+
+ public static abstract class Phone.Listener {
+ ctor public Phone.Listener();
+ method public void onAudioStateChanged(android.telecomm.Phone, android.telecomm.CallAudioState);
+ method public void onBringToForeground(android.telecomm.Phone, boolean);
+ method public void onCallAdded(android.telecomm.Phone, android.telecomm.Call);
+ method public void onCallRemoved(android.telecomm.Phone, android.telecomm.Call);
+ }
+
+ public class PhoneAccount implements android.os.Parcelable {
+ ctor public PhoneAccount(android.content.ComponentName, java.lang.String, android.net.Uri, int);
method public int describeContents();
+ method public int getCapabilities();
method public android.content.ComponentName getComponentName();
method public android.net.Uri getHandle();
- method public android.graphics.drawable.Drawable getIcon(android.content.Context);
- method public android.graphics.drawable.Drawable getIcon(android.content.Context, int);
method public java.lang.String getId();
- method public java.lang.String getLabel(android.content.Context);
- method public java.lang.String getShortDescription(android.content.Context);
- method public boolean isEnabled();
- method public boolean isSystemDefault();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2
+ field public static final int CAPABILITY_SIM_CALL_MANAGER = 1; // 0x1
+ field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public class PhoneAccountMetadata implements android.os.Parcelable {
+ ctor public PhoneAccountMetadata(android.telecomm.PhoneAccount, int, java.lang.String, java.lang.String);
+ method public int describeContents();
+ method public android.telecomm.PhoneAccount getAccount();
+ method public android.graphics.drawable.Drawable getIcon(android.content.Context);
+ method public java.lang.String getLabel();
+ method public java.lang.String getShortDescription();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
@@ -28136,18 +28293,17 @@
method public void updatePeerDimensions(int, int) throws android.os.RemoteException;
}
- public class RemoteCallVideoProvider implements android.os.IBinder.DeathRecipient {
- method public void binderDied();
- method public void requestCallDataUsage() throws android.os.RemoteException;
- method public void requestCameraCapabilities() throws android.os.RemoteException;
- method public void sendSessionModifyRequest(android.telecomm.VideoCallProfile) throws android.os.RemoteException;
- method public void sendSessionModifyResponse(android.telecomm.VideoCallProfile) throws android.os.RemoteException;
- method public void setCallVideoClient(android.telecomm.CallVideoClient) throws android.os.RemoteException;
+ public class RemoteCallVideoProvider {
+ method public void requestCallDataUsage();
+ method public void requestCameraCapabilities();
+ method public void sendSessionModifyRequest(android.telecomm.VideoCallProfile);
+ method public void sendSessionModifyResponse(android.telecomm.VideoCallProfile);
+ method public void setCallVideoClient(android.telecomm.CallVideoClient);
method public void setCamera(java.lang.String) throws android.os.RemoteException;
- method public void setDeviceOrientation(int) throws android.os.RemoteException;
- method public void setDisplaySurface(android.view.Surface) throws android.os.RemoteException;
- method public void setPauseImage(java.lang.String) throws android.os.RemoteException;
- method public void setPreviewSurface(android.view.Surface) throws android.os.RemoteException;
+ method public void setDeviceOrientation(int);
+ method public void setDisplaySurface(android.view.Surface);
+ method public void setPauseImage(java.lang.String);
+ method public void setPreviewSurface(android.view.Surface);
method public void setZoom(float) throws android.os.RemoteException;
}
@@ -28214,16 +28370,16 @@
public final class TelecommConstants {
ctor public TelecommConstants();
- field public static final java.lang.String ACTION_CALL_SERVICE_PROVIDER;
field public static final java.lang.String ACTION_CONNECTION_SERVICE;
+ field public static final java.lang.String ACTION_CONNECTION_SERVICE_CONFIGURE = "android.intent.action.CONNECTION_SERVICE_CONFIGURE";
field public static final java.lang.String ACTION_INCOMING_CALL = "android.intent.action.INCOMING_CALL";
field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecomm.extra.CALL_DISCONNECT_CAUSE";
field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecomm.extra.CALL_DISCONNECT_MESSAGE";
- field public static final java.lang.String EXTRA_CALL_SERVICE_DESCRIPTOR = "android.intent.extra.CALL_SERVICE_DESCRIPTOR";
field public static final java.lang.String EXTRA_CONNECTION_SERVICE = "android.telecomm.extra.CONNECTION_SERVICE";
field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.intent.extra.INCOMING_CALL_EXTRAS";
+ field public static final java.lang.String EXTRA_PHONE_ACCOUNT = "android.intent.extra.PHONE_ACCOUNT";
field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.intent.extra.START_CALL_WITH_SPEAKERPHONE";
field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.intent.extra.START_CALL_WITH_VIDEO_STATE";
}
@@ -28662,7 +28818,6 @@
}
public class TelephonyManager {
- method public java.util.List<android.telecomm.PhoneAccount> getAccounts();
method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
method public int getCallState();
method public android.telephony.CellLocation getCellLocation();
@@ -28711,7 +28866,6 @@
field public static final int DATA_CONNECTING = 1; // 0x1
field public static final int DATA_DISCONNECTED = 0; // 0x0
field public static final int DATA_SUSPENDED = 3; // 0x3
- field public static final java.lang.String EXTRA_ACCOUNT = "account";
field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
field public static final java.lang.String EXTRA_STATE = "state";
field public static final java.lang.String EXTRA_STATE_IDLE;
@@ -29227,6 +29381,7 @@
method public java.io.File getFileStreamPath(java.lang.String);
method public java.io.File getFilesDir();
method public android.os.Looper getMainLooper();
+ method public java.io.File getNoBackupFilesDir();
method public java.io.File getObbDir();
method public java.io.File[] getObbDirs();
method public java.lang.String getPackageCodePath();
@@ -29363,8 +29518,10 @@
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+ method public android.content.pm.PackageInstaller getInstaller();
method public java.lang.String getInstallerPackageName(java.lang.String);
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
method public java.lang.String getNameForUid(int);
@@ -29382,12 +29539,15 @@
method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public android.content.pm.KeySet getSigningKeySet(java.lang.String);
method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
method public java.lang.String[] getSystemSharedLibraryNames();
method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public boolean hasSystemFeature(java.lang.String);
method public boolean isSafeMode();
+ method public boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
+ method public boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
@@ -34277,6 +34437,7 @@
method protected final int getForcedWindowFlags();
method public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
+ method public android.media.session.MediaController getMediaController();
method public abstract int getNavigationBarColor();
method public android.transition.Transition getSharedElementEnterTransition();
method public android.transition.Transition getSharedElementExitTransition();
@@ -34333,6 +34494,7 @@
method public void setLayout(int, int);
method public void setLocalFocus(boolean, boolean);
method public void setLogo(int);
+ method public void setMediaController(android.media.session.MediaController);
method public abstract void setNavigationBarColor(int);
method public void setSharedElementEnterTransition(android.transition.Transition);
method public void setSharedElementExitTransition(android.transition.Transition);
@@ -36999,8 +37161,16 @@
ctor public DatePicker(android.content.Context, android.util.AttributeSet);
ctor public DatePicker(android.content.Context, android.util.AttributeSet, int);
ctor public DatePicker(android.content.Context, android.util.AttributeSet, int, int);
+ method public android.content.res.ColorStateList getCalendarTextColor();
method public android.widget.CalendarView getCalendarView();
method public boolean getCalendarViewShown();
+ method public int getDateSelectorBackgroundColor();
+ method public int getDateSelectorDayOfMonthTextAppearance();
+ method public int getDateSelectorDayOfWeekBackgroundColor();
+ method public int getDateSelectorDayOfWeekTextAppearance();
+ method public int getDateSelectorMonthTextAppearance();
+ method public int getDateSelectorYearListItemTextAppearance();
+ method public int getDateSelectorYearTextAppearance();
method public int getDayOfMonth();
method public long getMaxDate();
method public long getMinDate();
@@ -37008,7 +37178,15 @@
method public boolean getSpinnersShown();
method public int getYear();
method public void init(int, int, int, android.widget.DatePicker.OnDateChangedListener);
+ method public void setCalendarTextColor(android.content.res.ColorStateList);
method public void setCalendarViewShown(boolean);
+ method public void setDateSelectorBackgroundColor(int);
+ method public void setDateSelectorDayOfMonthTextAppearance(int);
+ method public void setDateSelectorDayOfWeekBackgroundColor(int);
+ method public void setDateSelectorDayOfWeekTextAppearance(int);
+ method public void setDateSelectorMonthTextAppearance(int);
+ method public void setDateSelectorYearListItemTextAppearance(int);
+ method public void setDateSelectorYearTextAppearance(int);
method public void setMaxDate(long);
method public void setMinDate(long);
method public void setSpinnersShown(boolean);
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 3d0eec4..b7f1ff9 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -50,6 +50,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.content.PackageHelper;
@@ -923,33 +924,13 @@
return;
}
} else if (opt.equals("--abi")) {
- abi = nextOptionData();
- if (abi == null) {
- System.err.println("Error: must supply argument for --abi");
- return;
- }
+ abi = checkAbiArgument(nextOptionData());
} else {
System.err.println("Error: Unknown option: " + opt);
return;
}
}
- if (abi != null) {
- final String[] supportedAbis = Build.SUPPORTED_ABIS;
- boolean matched = false;
- for (String supportedAbi : supportedAbis) {
- if (supportedAbi.equals(abi)) {
- matched = true;
- break;
- }
- }
-
- if (!matched) {
- System.err.println("Error: abi " + abi + " not supported on this device.");
- return;
- }
- }
-
final Uri verificationURI;
final Uri originatingURI;
final Uri referrerURI;
@@ -1017,7 +998,7 @@
final InstallSessionParams params = new InstallSessionParams();
params.installFlags = PackageManager.INSTALL_ALL_USERS;
- params.fullInstall = true;
+ params.mode = InstallSessionParams.MODE_FULL_INSTALL;
params.progressMax = -1;
String opt;
@@ -1040,10 +1021,12 @@
} else if (opt.equals("-d")) {
params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
} else if (opt.equals("-p")) {
- params.fullInstall = false;
+ params.mode = InstallSessionParams.MODE_INHERIT_EXISTING;
} else if (opt.equals("-S")) {
params.deltaSize = Long.parseLong(nextOptionData());
params.progressMax = (int) params.deltaSize;
+ } else if (opt.equals("--abi")) {
+ params.abiOverride = checkAbiArgument(nextOptionData());
} else {
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -1684,6 +1667,21 @@
}
}
+ private static String checkAbiArgument(String abi) {
+ if (TextUtils.isEmpty(abi)) {
+ throw new IllegalArgumentException("Missing ABI argument");
+ }
+
+ final String[] supportedAbis = Build.SUPPORTED_ABIS;
+ for (String supportedAbi : supportedAbis) {
+ if (supportedAbi.equals(abi)) {
+ return abi;
+ }
+ }
+
+ throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
+ }
+
private String nextOption() {
if (mNextArg >= mArgs.length) {
return null;
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index bf2924c..bdfbde1 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -631,6 +631,9 @@
public void setObjectValues(Object... values) {
mValueType = values[0].getClass();
mKeyframeSet = KeyframeSet.ofObject(values);
+ if (mEvaluator != null) {
+ mKeyframeSet.setEvaluator(mEvaluator);
+ }
}
/**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d20a5dc..cac646d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -23,6 +23,7 @@
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
import android.widget.Toolbar;
+
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.app.ToolbarActionBar;
@@ -51,6 +52,8 @@
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -109,14 +112,14 @@
* or embedded inside of another activity (using {@link ActivityGroup}).
*
* There are two methods almost all subclasses of Activity will implement:
- *
+ *
* <ul>
* <li> {@link #onCreate} is where you initialize your activity. Most
* importantly, here you will usually call {@link #setContentView(int)}
* with a layout resource defining your UI, and using {@link #findViewById}
* to retrieve the widgets in that UI that you need to interact with
* programmatically.
- *
+ *
* <li> {@link #onPause} is where you deal with the user leaving your
* activity. Most importantly, any changes made by the user should at this
* point be committed (usually to the
@@ -127,7 +130,7 @@
* activity classes must have a corresponding
* {@link android.R.styleable#AndroidManifestActivity <activity>}
* declaration in their package's <code>AndroidManifest.xml</code>.</p>
- *
+ *
* <p>Topics covered here:
* <ol>
* <li><a href="#Fragments">Fragments</a>
@@ -170,14 +173,14 @@
* and becomes the running activity -- the previous activity always remains
* below it in the stack, and will not come to the foreground again until
* the new activity exits.</p>
- *
+ *
* <p>An activity has essentially four states:</p>
* <ul>
* <li> If an activity in the foreground of the screen (at the top of
* the stack),
* it is <em>active</em> or <em>running</em>. </li>
* <li>If an activity has lost focus but is still visible (that is, a new non-full-sized
- * or transparent activity has focus on top of your activity), it
+ * or transparent activity has focus on top of your activity), it
* is <em>paused</em>. A paused activity is completely alive (it
* maintains all state and member information and remains attached to
* the window manager), but can be killed by the system in extreme
@@ -197,13 +200,13 @@
* The square rectangles represent callback methods you can implement to
* perform operations when the Activity moves between states. The colored
* ovals are major states the Activity can be in.</p>
- *
+ *
* <p><img src="../../../images/activity_lifecycle.png"
* alt="State diagram for an Android Activity Lifecycle." border="0" /></p>
- *
+ *
* <p>There are three key loops you may be interested in monitoring within your
* activity:
- *
+ *
* <ul>
* <li>The <b>entire lifetime</b> of an activity happens between the first call
* to {@link android.app.Activity#onCreate} through to a single final call
@@ -212,7 +215,7 @@
* onDestroy(). For example, if it has a thread running in the background
* to download data from the network, it may create that thread in onCreate()
* and then stop the thread in onDestroy().
- *
+ *
* <li>The <b>visible lifetime</b> of an activity happens between a call to
* {@link android.app.Activity#onStart} until a corresponding call to
* {@link android.app.Activity#onStop}. During this time the user can see the
@@ -224,7 +227,7 @@
* longer sees what you are displaying. The onStart() and onStop() methods
* can be called multiple times, as the activity becomes visible and hidden
* to the user.
- *
+ *
* <li>The <b>foreground lifetime</b> of an activity happens between a call to
* {@link android.app.Activity#onResume} until a corresponding call to
* {@link android.app.Activity#onPause}. During this time the activity is
@@ -234,7 +237,7 @@
* intent is delivered -- so the code in these methods should be fairly
* lightweight.
* </ul>
- *
+ *
* <p>The entire lifecycle of an activity is defined by the following
* Activity methods. All of these are hooks that you can override
* to do appropriate work when the activity changes state. All
@@ -250,7 +253,7 @@
* protected void onCreate(Bundle savedInstanceState);
*
* protected void onStart();
- *
+ *
* protected void onRestart();
*
* protected void onResume();
@@ -366,7 +369,7 @@
* {@link #onSaveInstanceState(Bundle)} is called before placing the activity
* in such a background state, allowing you to save away any dynamic instance
* state in your activity into the given Bundle, to be later received in
- * {@link #onCreate} if the activity needs to be re-created.
+ * {@link #onCreate} if the activity needs to be re-created.
* See the <a href="#ProcessLifecycle">Process Lifecycle</a>
* section for more information on how the lifecycle of a process is tied
* to the activities it is hosting. Note that it is important to save
@@ -390,14 +393,14 @@
*
* <a name="ConfigurationChanges"></a>
* <h3>Configuration Changes</h3>
- *
+ *
* <p>If the configuration of the device (as defined by the
* {@link Configuration Resources.Configuration} class) changes,
* then anything displaying a user interface will need to update to match that
* configuration. Because Activity is the primary mechanism for interacting
* with the user, it includes special support for handling configuration
* changes.</p>
- *
+ *
* <p>Unless you specify otherwise, a configuration change (such as a change
* in screen orientation, language, input devices, etc) will cause your
* current activity to be <em>destroyed</em>, going through the normal activity
@@ -407,7 +410,7 @@
* called in that instance then a new instance of the activity will be
* created, with whatever savedInstanceState the previous instance had generated
* from {@link #onSaveInstanceState}.</p>
- *
+ *
* <p>This is done because any application resource,
* including layout files, can change based on any configuration value. Thus
* the only safe way to handle a configuration change is to re-retrieve all
@@ -415,7 +418,7 @@
* must already know how to save their state and re-create themselves from
* that state, this is a convenient way to have an activity restart itself
* with a new configuration.</p>
- *
+ *
* <p>In some special cases, you may want to bypass restarting of your
* activity based on one or more types of configuration changes. This is
* done with the {@link android.R.attr#configChanges android:configChanges}
@@ -425,7 +428,7 @@
* a configuration change involves any that you do not handle, however, the
* activity will still be restarted and {@link #onConfigurationChanged}
* will not be called.</p>
- *
+ *
* <a name="StartingActivities"></a>
* <h3>Starting Activities and Getting Results</h3>
*
@@ -440,10 +443,10 @@
* ends. For example, you may start an activity that lets the user pick
* a person in a list of contacts; when it ends, it returns the person
* that was selected. To do this, you call the
- * {@link android.app.Activity#startActivityForResult(Intent, int)}
- * version with a second integer parameter identifying the call. The result
+ * {@link android.app.Activity#startActivityForResult(Intent, int)}
+ * version with a second integer parameter identifying the call. The result
* will come back through your {@link android.app.Activity#onActivityResult}
- * method.</p>
+ * method.</p>
*
* <p>When an activity exits, it can call
* {@link android.app.Activity#setResult(int)}
@@ -570,17 +573,17 @@
*
* protected void onPause() {
* super.onPause();
- *
+ *
* SharedPreferences.Editor ed = mPrefs.edit();
* ed.putInt("view_mode", mCurViewMode);
* ed.commit();
* }
* }
* </pre>
- *
+ *
* <a name="Permissions"></a>
* <h3>Permissions</h3>
- *
+ *
* <p>The ability to start a particular Activity can be enforced when it is
* declared in its
* manifest's {@link android.R.styleable#AndroidManifestActivity <activity>}
@@ -601,10 +604,10 @@
*
* <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
* document for more information on permissions and security in general.
- *
+ *
* <a name="ProcessLifecycle"></a>
* <h3>Process Lifecycle</h3>
- *
+ *
* <p>The Android system attempts to keep application process around for as
* long as possible, but eventually will need to remove old processes when
* memory runs low. As described in <a href="#ActivityLifecycle">Activity
@@ -614,7 +617,7 @@
* listed here in order of importance. The system will kill less important
* processes (the last ones) before it resorts to killing more important
* processes (the first ones).
- *
+ *
* <ol>
* <li> <p>The <b>foreground activity</b> (the activity at the top of the screen
* that the user is currently interacting with) is considered the most important.
@@ -642,7 +645,7 @@
* context of an activity BroadcastReceiver or Service to ensure that the system
* knows it needs to keep your process around.
* </ol>
- *
+ *
* <p>Sometimes an Activity may need to do a long-running operation that exists
* independently of the activity lifecycle itself. An example may be a camera
* application that allows you to upload a picture to a web site. The upload
@@ -720,7 +723,7 @@
VoiceInteractor voiceInteractor;
}
/* package */ NonConfigurationInstances mLastNonConfigurationInstances;
-
+
private Window mWindow;
private WindowManager mWindowManager;
@@ -764,7 +767,7 @@
private final ArrayList<ManagedCursor> mManagedCursors =
new ArrayList<ManagedCursor>();
- // protected by synchronized (this)
+ // protected by synchronized (this)
int mResultCode = RESULT_CANCELED;
Intent mResultData = null;
@@ -775,7 +778,7 @@
private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE;
private SpannableStringBuilder mDefaultKeySsb = null;
-
+
protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
@SuppressWarnings("unused")
@@ -793,16 +796,16 @@
return mIntent;
}
- /**
- * Change the intent returned by {@link #getIntent}. This holds a
- * reference to the given intent; it does not copy it. Often used in
- * conjunction with {@link #onNewIntent}.
- *
- * @param newIntent The new Intent object to return from getIntent
- *
+ /**
+ * Change the intent returned by {@link #getIntent}. This holds a
+ * reference to the given intent; it does not copy it. Often used in
+ * conjunction with {@link #onNewIntent}.
+ *
+ * @param newIntent The new Intent object to return from getIntent
+ *
* @see #getIntent
* @see #onNewIntent
- */
+ */
public void setIntent(Intent newIntent) {
mIntent = newIntent;
}
@@ -816,7 +819,7 @@
public final boolean isChild() {
return mParent != null;
}
-
+
/** Return the parent activity if this view is an embedded child. */
public final Activity getParent() {
return mParent;
@@ -831,7 +834,7 @@
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
- *
+ *
* @return Window The current window, or null if the activity is not
* visual.
*/
@@ -850,7 +853,7 @@
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
return mLoaderManager;
}
-
+
LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
if (mAllLoaderManagers == null) {
mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();
@@ -866,13 +869,13 @@
}
return lm;
}
-
+
/**
* Calls {@link android.view.Window#getCurrentFocus} on the
* Window of this Activity to return the currently focused view.
- *
+ *
* @return View The current View with focus or null.
- *
+ *
* @see #getWindow
* @see android.view.Window#getCurrentFocus
*/
@@ -888,20 +891,20 @@
* with widgets in the UI, calling
* {@link #managedQuery(android.net.Uri , String[], String, String[], String)} to retrieve
* cursors for data being displayed, etc.
- *
+ *
* <p>You can call {@link #finish} from within this function, in
* which case onDestroy() will be immediately called without any of the rest
* of the activity lifecycle ({@link #onStart}, {@link #onResume},
* {@link #onPause}, etc) executing.
- *
+ *
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @param savedInstanceState If the activity is being re-initialized after
* previously being shut down then this Bundle contains the data it most
* recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b>
- *
+ *
* @see #onStart
* @see #onSaveInstanceState
* @see #onRestoreInstanceState
@@ -996,12 +999,12 @@
* decide whether to use your default implementation. The default
* implementation of this method performs a restore of any view state that
* had previously been frozen by {@link #onSaveInstanceState}.
- *
+ *
* <p>This method is called between {@link #onStart} and
* {@link #onPostCreate}.
- *
+ *
* @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
- *
+ *
* @see #onCreate
* @see #onPostCreate
* @see #onResume
@@ -1098,11 +1101,11 @@
* and {@link #onRestoreInstanceState} have been called). Applications will
* generally not implement this method; it is intended for system
* classes to do final initialization after application code has run.
- *
+ *
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @param savedInstanceState If the activity is being re-initialized after
* previously being shut down then this Bundle contains the data it most
* recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b>
@@ -1133,14 +1136,14 @@
}
/**
- * Called after {@link #onCreate} — or after {@link #onRestart} when
- * the activity had been stopped, but is now again being displayed to the
+ * Called after {@link #onCreate} — or after {@link #onRestart} when
+ * the activity had been stopped, but is now again being displayed to the
* user. It will be followed by {@link #onResume}.
*
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @see #onCreate
* @see #onStop
* @see #onResume
@@ -1148,7 +1151,7 @@
protected void onStart() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
mCalled = true;
-
+
if (!mLoadersStarted) {
mLoadersStarted = true;
if (mLoaderManager != null) {
@@ -1173,11 +1176,11 @@
* this is usually the place
* where the cursor should be requeried (because you had deactivated it in
* {@link #onStop}.
- *
+ *
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @see #onStop
* @see #onStart
* @see #onResume
@@ -1200,7 +1203,7 @@
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @see #onRestoreInstanceState
* @see #onRestart
* @see #onPostResume
@@ -1218,11 +1221,11 @@
* been called). Applications will generally not implement this method;
* it is intended for system classes to do final setup after application
* resume code has run.
- *
+ *
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @see #onResume
*/
protected void onPostResume() {
@@ -1256,19 +1259,19 @@
* activity is re-launched while at the top of the activity stack instead
* of a new instance of the activity being started, onNewIntent() will be
* called on the existing instance with the Intent that was used to
- * re-launch it.
- *
- * <p>An activity will always be paused before receiving a new intent, so
- * you can count on {@link #onResume} being called after this method.
- *
- * <p>Note that {@link #getIntent} still returns the original Intent. You
- * can use {@link #setIntent} to update it to this new Intent.
- *
- * @param intent The new intent that was started for the activity.
- *
+ * re-launch it.
+ *
+ * <p>An activity will always be paused before receiving a new intent, so
+ * you can count on {@link #onResume} being called after this method.
+ *
+ * <p>Note that {@link #getIntent} still returns the original Intent. You
+ * can use {@link #setIntent} to update it to this new Intent.
+ *
+ * @param intent The new intent that was started for the activity.
+ *
* @see #getIntent
- * @see #setIntent
- * @see #onResume
+ * @see #setIntent
+ * @see #onResume
*/
protected void onNewIntent(Intent intent) {
}
@@ -1342,9 +1345,9 @@
*
* <p>If called, this method will occur before {@link #onStop}. There are
* no guarantees about whether it will occur before or after {@link #onPause}.
- *
+ *
* @param outState Bundle in which to place your saved state.
- *
+ *
* @see #onCreate
* @see #onRestoreInstanceState
* @see #onPause
@@ -1429,23 +1432,23 @@
* noticeable amount of CPU in order to make the switch to the next activity
* as fast as possible, or to close resources that are exclusive access
* such as the camera.
- *
+ *
* <p>In situations where the system needs more memory it may kill paused
* processes to reclaim resources. Because of this, you should be sure
* that all of your state is saved by the time you return from
* this function. In general {@link #onSaveInstanceState} is used to save
* per-instance state in the activity and this method is used to store
* global persistent data (in content providers, files, etc.)
- *
+ *
* <p>After receiving this call you will usually receive a following call
* to {@link #onStop} (after the next activity has been resumed and
* displayed), however in some cases there will be a direct call back to
* {@link #onResume} without going through the stopped state.
- *
+ *
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @see #onResume
* @see #onSaveInstanceState
* @see #onStop
@@ -1464,32 +1467,32 @@
* brought to the foreground, {@link #onUserLeaveHint} will not be called on
* the activity being interrupted. In cases when it is invoked, this method
* is called right before the activity's {@link #onPause} callback.
- *
+ *
* <p>This callback and {@link #onUserInteraction} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
- *
+ *
* @see #onUserInteraction()
*/
protected void onUserLeaveHint() {
}
-
+
/**
* Generate a new thumbnail for this activity. This method is called before
* pausing the activity, and should draw into <var>outBitmap</var> the
* imagery for the desired thumbnail in the dimensions of that bitmap. It
* can use the given <var>canvas</var>, which is configured to draw into the
* bitmap, for rendering if desired.
- *
+ *
* <p>The default implementation returns fails and does not draw a thumbnail;
* this will result in the platform creating its own thumbnail if needed.
- *
+ *
* @param outBitmap The bitmap to contain the thumbnail.
* @param canvas Can be used to render into the bitmap.
- *
+ *
* @return Return true if you have drawn into the bitmap; otherwise after
* you return it will be filled with a default thumbnail.
- *
+ *
* @see #onCreateDescription
* @see #onSaveInstanceState
* @see #onPause
@@ -1502,15 +1505,15 @@
* Generate a new description for this activity. This method is called
* before pausing the activity and can, if desired, return some textual
* description of its current state to be displayed to the user.
- *
+ *
* <p>The default implementation returns null, which will cause you to
* inherit the description from the previous activity. If all activities
* return null, generally the label of the top activity will be used as the
* description.
- *
+ *
* @return A description of what the user is doing. It should be short and
* sweet (only a few words).
- *
+ *
* @see #onCreateThumbnail
* @see #onSaveInstanceState
* @see #onPause
@@ -1538,15 +1541,15 @@
* Called when you are no longer visible to the user. You will next
* receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
* depending on later user activity.
- *
+ *
* <p>Note that this method may never be called, in low memory situations
* where the system does not have enough memory to keep your activity's
* process running after its {@link #onPause} method is called.
- *
+ *
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @see #onRestart
* @see #onResume
* @see #onSaveInstanceState
@@ -1567,7 +1570,7 @@
* {@link #finish} on it, or because the system is temporarily destroying
* this instance of the activity to save space. You can distinguish
* between these two scenarios with the {@link #isFinishing} method.
- *
+ *
* <p><em>Note: do not count on this method being called as a place for
* saving data! For example, if an activity is editing data in a content
* provider, those edits should be committed in either {@link #onPause} or
@@ -1579,11 +1582,11 @@
* calling this method (or any others) in it, so it should not be used to
* do things that are intended to remain around after the process goes
* away.
- *
+ *
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
- *
+ *
* @see #onPause
* @see #onStop
* @see #finish
@@ -1657,11 +1660,11 @@
* by that attribute, then instead of reporting it the system will stop
* and restart the activity (to have it launched with the new
* configuration).
- *
+ *
* <p>At the time that this function has been called, your Resources
* object will have been updated to return resource values matching the
* new configuration.
- *
+ *
* @param newConfig The new device configuration.
*/
public void onConfigurationChanged(Configuration newConfig) {
@@ -1681,7 +1684,7 @@
mActionBar.onConfigurationChanged(newConfig);
}
}
-
+
/**
* If this activity is being destroyed because it can not handle a
* configuration parameter being changed (and thus its
@@ -1691,7 +1694,7 @@
* destroyed. Note that there is no guarantee that these will be
* accurate (other changes could have happened at any time), so you should
* only use this as an optimization hint.
- *
+ *
* @return Returns a bit field of the configuration parameters that are
* changing, as defined by the {@link android.content.res.Configuration}
* class.
@@ -1699,21 +1702,21 @@
public int getChangingConfigurations() {
return mConfigChangeFlags;
}
-
+
/**
* Retrieve the non-configuration instance data that was previously
* returned by {@link #onRetainNonConfigurationInstance()}. This will
* be available from the initial {@link #onCreate} and
* {@link #onStart} calls to the new instance, allowing you to extract
* any useful dynamic state from the previous instance.
- *
+ *
* <p>Note that the data you retrieve here should <em>only</em> be used
* as an optimization for handling configuration changes. You should always
* be able to handle getting a null pointer back, and an activity must
* still be able to restore itself to its previous state (through the
* normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
* function returns null.
- *
+ *
* @return Returns the object previously returned by
* {@link #onRetainNonConfigurationInstance()}.
*
@@ -1727,7 +1730,7 @@
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
-
+
/**
* Called by the system, as part of destroying an
* activity due to a configuration change, when it is known that a new
@@ -1736,7 +1739,7 @@
* itself, which can later be retrieved by calling
* {@link #getLastNonConfigurationInstance()} in the new activity
* instance.
- *
+ *
* <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
* or later, consider instead using a {@link Fragment} with
* {@link Fragment#setRetainInstance(boolean)
@@ -1756,14 +1759,14 @@
* the {@link #getLastNonConfigurationInstance()} method of the following
* activity instance as described there.
* </ul>
- *
+ *
* <p>These guarantees are designed so that an activity can use this API
* to propagate extensive state from the old to new activity instance, from
* loaded bitmaps, to network connections, to evenly actively running
* threads. Note that you should <em>not</em> propagate any data that
* may change based on the configuration, including any data loaded from
* resources such as strings, layouts, or drawables.
- *
+ *
* <p>The guarantee of no message handling during the switch to the next
* activity simplifies use with active objects. For example if your retained
* state is an {@link android.os.AsyncTask} you are guaranteed that its
@@ -1783,21 +1786,21 @@
public Object onRetainNonConfigurationInstance() {
return null;
}
-
+
/**
* Retrieve the non-configuration instance data that was previously
* returned by {@link #onRetainNonConfigurationChildInstances()}. This will
* be available from the initial {@link #onCreate} and
* {@link #onStart} calls to the new instance, allowing you to extract
* any useful dynamic state from the previous instance.
- *
+ *
* <p>Note that the data you retrieve here should <em>only</em> be used
* as an optimization for handling configuration changes. You should always
* be able to handle getting a null pointer back, and an activity must
* still be able to restore itself to its previous state (through the
* normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
* function returns null.
- *
+ *
* @return Returns the object previously returned by
* {@link #onRetainNonConfigurationChildInstances()}
*/
@@ -1806,7 +1809,7 @@
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.children : null;
}
-
+
/**
* This method is similar to {@link #onRetainNonConfigurationInstance()} except that
* it should return either a mapping from child activity id strings to arbitrary objects,
@@ -1818,7 +1821,7 @@
HashMap<String,Object> onRetainNonConfigurationChildInstances() {
return null;
}
-
+
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
@@ -1846,7 +1849,7 @@
&& mVoiceInteractor == null) {
return null;
}
-
+
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
@@ -1886,7 +1889,7 @@
}
}
}
-
+
/**
* Called when a Fragment is being attached to this activity, immediately
* after the call to its {@link Fragment#onAttach Fragment.onAttach()}
@@ -1894,14 +1897,14 @@
*/
public void onAttachFragment(Fragment fragment) {
}
-
+
/**
* Wrapper around
* {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
* that gives the resulting {@link Cursor} to call
* {@link #startManagingCursor} so that the activity will manage its
* lifecycle for you.
- *
+ *
* <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
* or later, consider instead using {@link LoaderManager} instead, available
* via {@link #getLoaderManager()}.</em>
@@ -1911,14 +1914,14 @@
* you call {@link #stopManagingCursor} on a cursor from a managed query, the system <em>will
* not</em> automatically close the cursor and, in that case, you must call
* {@link Cursor#close()}.</p>
- *
+ *
* @param uri The URI of the content provider to query.
* @param projection List of columns to return.
* @param selection SQL WHERE clause.
* @param sortOrder SQL ORDER BY clause.
- *
+ *
* @return The Cursor that was returned by query().
- *
+ *
* @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
* @see #startManagingCursor
* @hide
@@ -1941,7 +1944,7 @@
* that gives the resulting {@link Cursor} to call
* {@link #startManagingCursor} so that the activity will manage its
* lifecycle for you.
- *
+ *
* <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
* or later, consider instead using {@link LoaderManager} instead, available
* via {@link #getLoaderManager()}.</em>
@@ -1951,15 +1954,15 @@
* you call {@link #stopManagingCursor} on a cursor from a managed query, the system <em>will
* not</em> automatically close the cursor and, in that case, you must call
* {@link Cursor#close()}.</p>
- *
+ *
* @param uri The URI of the content provider to query.
* @param projection List of columns to return.
* @param selection SQL WHERE clause.
* @param selectionArgs The arguments to selection, if any ?s are pesent
* @param sortOrder SQL ORDER BY clause.
- *
+ *
* @return The Cursor that was returned by query().
- *
+ *
* @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
* @see #startManagingCursor
*
@@ -1982,7 +1985,7 @@
* {@link Cursor#deactivate} on the given Cursor, and when it is later restarted
* it will call {@link Cursor#requery} for you. When the activity is
* destroyed, all managed Cursors will be closed automatically.
- *
+ *
* <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
* or later, consider instead using {@link LoaderManager} instead, available
* via {@link #getLoaderManager()}.</em>
@@ -1992,9 +1995,9 @@
* However, if you call {@link #stopManagingCursor} on a cursor from a managed query, the system
* <em>will not</em> automatically close the cursor and, in that case, you must call
* {@link Cursor#close()}.</p>
- *
+ *
* @param c The Cursor to be managed.
- *
+ *
* @see #managedQuery(android.net.Uri , String[], String, String[], String)
* @see #stopManagingCursor
*
@@ -2013,13 +2016,13 @@
* Given a Cursor that was previously given to
* {@link #startManagingCursor}, stop the activity's management of that
* cursor.
- *
+ *
* <p><strong>Warning:</strong> After calling this method on a cursor from a managed query,
- * the system <em>will not</em> automatically close the cursor and you must call
+ * the system <em>will not</em> automatically close the cursor and you must call
* {@link Cursor#close()}.</p>
- *
+ *
* @param c The Cursor that was being managed.
- *
+ *
* @see #startManagingCursor
*
* @deprecated Use the new {@link android.content.CursorLoader} class with
@@ -2058,7 +2061,7 @@
public View findViewById(int id) {
return getWindow().findViewById(id);
}
-
+
/**
* Retrieve a reference to this activity's ActionBar.
*
@@ -2094,7 +2097,7 @@
mActionBar = new ToolbarActionBar(toolbar, getTitle(), this);
mActionBar.invalidateOptionsMenu();
}
-
+
/**
* Creates a new ActionBar, locates the inflated ActionBarView,
* initializes the ActionBar with the view, and sets mActionBar.
@@ -2116,13 +2119,13 @@
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
-
+
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
- *
+ *
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
@@ -2140,7 +2143,7 @@
* your own layout parameters, invoke
* {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
* instead.
- *
+ *
* @param view The desired content to display.
*
* @see #setContentView(int)
@@ -2155,7 +2158,7 @@
* Set the activity content to an explicit view. This view is placed
* directly into the activity's view hierarchy. It can itself be a complex
* view hierarchy.
- *
+ *
* @param view The desired content to display.
* @param params Layout parameters for the view.
*
@@ -2170,7 +2173,7 @@
/**
* Add an additional content view to the activity. Added after any existing
* ones in the activity -- existing views are NOT removed.
- *
+ *
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
@@ -2235,23 +2238,23 @@
/**
* Use with {@link #setDefaultKeyMode} to turn off default handling of
* keys.
- *
+ *
* @see #setDefaultKeyMode
*/
static public final int DEFAULT_KEYS_DISABLE = 0;
/**
* Use with {@link #setDefaultKeyMode} to launch the dialer during default
* key handling.
- *
+ *
* @see #setDefaultKeyMode
*/
static public final int DEFAULT_KEYS_DIALER = 1;
/**
* Use with {@link #setDefaultKeyMode} to execute a menu shortcut in
* default key handling.
- *
+ *
* <p>That is, the user does not need to hold down the menu key to execute menu shortcuts.
- *
+ *
* @see #setDefaultKeyMode
*/
static public final int DEFAULT_KEYS_SHORTCUT = 2;
@@ -2259,9 +2262,9 @@
* Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
* will start an application-defined search. (If the application or activity does not
* actually define a search, the the keys will be ignored.)
- *
+ *
* <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
- *
+ *
* @see #setDefaultKeyMode
*/
static public final int DEFAULT_KEYS_SEARCH_LOCAL = 3;
@@ -2270,9 +2273,9 @@
* Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
* will start a global search (typically web search, but some platforms may define alternate
* methods for global search)
- *
+ *
* <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
- *
+ *
* @see #setDefaultKeyMode
*/
static public final int DEFAULT_KEYS_SEARCH_GLOBAL = 4;
@@ -2284,16 +2287,16 @@
* floor. Other modes allow you to launch the dialer
* ({@link #DEFAULT_KEYS_DIALER}), execute a shortcut in your options
* menu without requiring the menu key be held down
- * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL}
+ * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL}
* and {@link #DEFAULT_KEYS_SEARCH_GLOBAL}).
- *
+ *
* <p>Note that the mode selected here does not impact the default
* handling of system keys, such as the "back" and "menu" keys, and your
* activity and its views always get a first chance to receive and handle
* all application keys.
- *
+ *
* @param mode The desired default key mode constant.
- *
+ *
* @see #DEFAULT_KEYS_DISABLE
* @see #DEFAULT_KEYS_DIALER
* @see #DEFAULT_KEYS_SHORTCUT
@@ -2303,7 +2306,7 @@
*/
public final void setDefaultKeyMode(@DefaultKeyMode int mode) {
mDefaultKeyMode = mode;
-
+
// Some modes use a SpannableStringBuilder to track & dispatch input events
// This list must remain in sync with the switch in onKeyDown()
switch (mode) {
@@ -2324,10 +2327,10 @@
/**
* Called when a key was pressed down and not handled by any of the views
- * inside of the activity. So, for example, key presses while the cursor
+ * inside of the activity. So, for example, key presses while the cursor
* is inside a TextView will not trigger the event (unless it is a navigation
* to another object) because TextView handles its own key presses.
- *
+ *
* <p>If the focused view didn't want this event, this method is called.
*
* <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK}
@@ -2338,12 +2341,12 @@
* will be performed; for earlier applications, it will perform the
* action immediately in on-down, as those versions of the platform
* behaved.
- *
+ *
* <p>Other additional default key handling may be performed
* if configured with {@link #setDefaultKeyMode}.
- *
+ *
* @return Return <code>true</code> to prevent this event from being propagated
- * further, or <code>false</code> to indicate that you have not handled
+ * further, or <code>false</code> to indicate that you have not handled
* this event and it should continue to be propagated.
* @see #onKeyUp
* @see android.view.KeyEvent
@@ -2358,11 +2361,11 @@
}
return true;
}
-
+
if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
return false;
} else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
- if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
+ if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
return true;
}
@@ -2382,12 +2385,12 @@
final String str = mDefaultKeySsb.toString();
clearSpannable = true;
-
+
switch (mDefaultKeyMode) {
case DEFAULT_KEYS_DIALER:
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
+ startActivity(intent);
break;
case DEFAULT_KEYS_SEARCH_LOCAL:
startSearch(str, false, null, false);
@@ -2418,16 +2421,16 @@
/**
* Called when a key was released and not handled by any of the views
- * inside of the activity. So, for example, key presses while the cursor
+ * inside of the activity. So, for example, key presses while the cursor
* is inside a TextView will not trigger the event (unless it is a navigation
* to another object) because TextView handles its own key presses.
- *
+ *
* <p>The default implementation handles KEYCODE_BACK to stop the activity
* and go back.
- *
+ *
* @return Return <code>true</code> to prevent this event from being propagated
- * further, or <code>false</code> to indicate that you have not handled
- * this event and it should continue to be propagated.
+ * further, or <code>false</code> to indicate that you have not handled
+ * this event and it should continue to be propagated.
* @see #onKeyDown
* @see KeyEvent
*/
@@ -2451,7 +2454,7 @@
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
return false;
}
-
+
/**
* Called when the activity has detected the user's press of the back
* key. The default implementation simply finishes the current activity,
@@ -2485,9 +2488,9 @@
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
- *
+ *
* @param event The touch screen event being processed.
- *
+ *
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
@@ -2496,10 +2499,10 @@
finish();
return true;
}
-
+
return false;
}
-
+
/**
* Called when the trackball was moved and not handled by any of the
* views inside of the activity. So, for example, if the trackball moves
@@ -2508,9 +2511,9 @@
* here happens <em>before</em> trackball movements are converted to
* DPAD key events, which then get sent back to the view hierarchy, and
* will be processed at the point for things like focus navigation.
- *
+ *
* @param event The trackball event being processed.
- *
+ *
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
@@ -2554,21 +2557,21 @@
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
- *
+ *
* <p>All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
- *
+ *
* <p>Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
- *
+ *
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}
-
+
public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
// Update window manager if: we have a view, that view is
// attached to its parent (which will be a RootView), and
@@ -2589,14 +2592,14 @@
* focus. This is the best indicator of whether this activity is visible
* to the user. The default implementation clears the key tracking
* state, so should always be called.
- *
+ *
* <p>Note that this provides information about global focus state, which
* is managed independently of activity lifecycles. As such, while focus
* changes will generally have some relation to lifecycle changes (an
* activity that is stopped will not generally get window focus), you
* should not rely on any particular order between the callbacks here and
* those in the other lifecycle methods such as {@link #onResume}.
- *
+ *
* <p>As a general rule, however, a resumed activity will have window
* focus... unless it has displayed other dialogs or popups that take
* input focus, in which case the activity itself will not have focus
@@ -2606,14 +2609,14 @@
* pausing the foreground activity.
*
* @param hasFocus Whether the window of this activity has focus.
- *
+ *
* @see #hasWindowFocus()
* @see #onResume
* @see View#onWindowFocusChanged(boolean)
*/
public void onWindowFocusChanged(boolean hasFocus) {
}
-
+
/**
* Called when the main window associated with the activity has been
* attached to the window manager.
@@ -2623,7 +2626,7 @@
*/
public void onAttachedToWindow() {
}
-
+
/**
* Called when the main window associated with the activity has been
* detached from the window manager.
@@ -2633,13 +2636,13 @@
*/
public void onDetachedFromWindow() {
}
-
+
/**
* Returns true if this activity's <em>main</em> window currently has window focus.
* Note that this is not the same as the view itself having focus.
- *
+ *
* @return True if this activity's main window currently has window focus.
- *
+ *
* @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams)
*/
public boolean hasWindowFocus() {
@@ -2661,14 +2664,14 @@
public void onWindowDismissed() {
finish();
}
-
+
/**
- * Called to process key events. You can override this to intercept all
- * key events before they are dispatched to the window. Be sure to call
+ * Called to process key events. You can override this to intercept all
+ * key events before they are dispatched to the window. Be sure to call
* this implementation for key events that should be handled normally.
- *
+ *
* @param event The key event.
- *
+ *
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
@@ -2713,9 +2716,9 @@
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
- *
+ *
* @param ev The touch screen event.
- *
+ *
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
@@ -2727,15 +2730,15 @@
}
return onTouchEvent(ev);
}
-
+
/**
* Called to process trackball events. You can override this to
* intercept all trackball events before they are dispatched to the
* window. Be sure to call this implementation for trackball events
* that should be handled normally.
- *
+ *
* @param ev The trackball event.
- *
+ *
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTrackballEvent(MotionEvent ev) {
@@ -2830,7 +2833,7 @@
/**
* {@inheritDoc}
- *
+ *
* @return The default implementation returns true.
*/
public boolean onMenuOpened(int featureId, Menu menu) {
@@ -2880,7 +2883,7 @@
}
}
return false;
-
+
case Window.FEATURE_CONTEXT_MENU:
if(titleCondensed != null) {
EventLog.writeEvent(50000, 1, titleCondensed.toString());
@@ -2894,7 +2897,7 @@
return false;
}
}
-
+
/**
* Default implementation of
* {@link android.view.Window.Callback#onPanelClosed(int, Menu)} for
@@ -2910,7 +2913,7 @@
mFragments.dispatchOptionsMenuClosed(menu);
onOptionsMenuClosed(menu);
break;
-
+
case Window.FEATURE_CONTEXT_MENU:
onContextMenuClosed(menu);
break;
@@ -2932,32 +2935,32 @@
mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
}
}
-
+
/**
* Initialize the contents of the Activity's standard options menu. You
* should place your menu items in to <var>menu</var>.
- *
+ *
* <p>This is only called once, the first time the options menu is
* displayed. To update the menu every time it is displayed, see
* {@link #onPrepareOptionsMenu}.
- *
+ *
* <p>The default implementation populates the menu with standard system
- * menu items. These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that
- * they will be correctly ordered with application-defined menu items.
- * Deriving classes should always call through to the base implementation.
- *
+ * menu items. These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that
+ * they will be correctly ordered with application-defined menu items.
+ * Deriving classes should always call through to the base implementation.
+ *
* <p>You can safely hold on to <var>menu</var> (and any items created
* from it), making modifications to it as desired, until the next
* time onCreateOptionsMenu() is called.
- *
+ *
* <p>When you add items to the menu, you can implement the Activity's
* {@link #onOptionsItemSelected} method to handle them there.
- *
+ *
* @param menu The options menu in which you place your items.
- *
+ *
* @return You must return true for the menu to be displayed;
* if you return false it will not be shown.
- *
+ *
* @see #onPrepareOptionsMenu
* @see #onOptionsItemSelected
*/
@@ -2973,17 +2976,17 @@
* called right before the menu is shown, every time it is shown. You can
* use this method to efficiently enable/disable items or otherwise
* dynamically modify the contents.
- *
+ *
* <p>The default implementation updates the system menu items based on the
* activity's state. Deriving classes should always call through to the
* base class implementation.
- *
+ *
* @param menu The options menu as last shown or first initialized by
* onCreateOptionsMenu().
- *
+ *
* @return You must return true for the menu to be displayed;
* if you return false it will not be shown.
- *
+ *
* @see #onCreateOptionsMenu
*/
public boolean onPrepareOptionsMenu(Menu menu) {
@@ -3000,15 +3003,15 @@
* its Handler as appropriate). You can use this method for any items
* for which you would like to do processing without those other
* facilities.
- *
+ *
* <p>Derived classes should call through to the base class for it to
* perform the default menu handling.</p>
- *
+ *
* @param item The menu item that was selected.
- *
+ *
* @return boolean Return false to allow normal menu processing to
* proceed, true to consume it here.
- *
+ *
* @see #onCreateOptionsMenu
*/
public boolean onOptionsItemSelected(MenuItem item) {
@@ -3125,7 +3128,7 @@
/**
* This hook is called whenever the options menu is being closed (either by the user canceling
* the menu with the back/menu button, or when an item is selected).
- *
+ *
* @param menu The options menu as last shown or first initialized by
* onCreateOptionsMenu().
*/
@@ -3134,7 +3137,7 @@
mParent.onOptionsMenuClosed(menu);
}
}
-
+
/**
* Programmatically opens the options menu. If the options menu is already
* open, this method does nothing.
@@ -3144,7 +3147,7 @@
mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
}
}
-
+
/**
* Progammatically closes the options menu. If the options menu is already
* closed, this method does nothing.
@@ -3175,43 +3178,43 @@
* {@link OnCreateContextMenuListener} on the view to this activity, so
* {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be
* called when it is time to show the context menu.
- *
+ *
* @see #unregisterForContextMenu(View)
* @param view The view that should show a context menu.
*/
public void registerForContextMenu(View view) {
view.setOnCreateContextMenuListener(this);
}
-
+
/**
* Prevents a context menu to be shown for the given view. This method will remove the
* {@link OnCreateContextMenuListener} on the view.
- *
+ *
* @see #registerForContextMenu(View)
* @param view The view that should stop showing a context menu.
*/
public void unregisterForContextMenu(View view) {
view.setOnCreateContextMenuListener(null);
}
-
+
/**
* Programmatically opens the context menu for a particular {@code view}.
* The {@code view} should have been added via
* {@link #registerForContextMenu(View)}.
- *
+ *
* @param view The view to show the context menu for.
*/
public void openContextMenu(View view) {
view.showContextMenu();
}
-
+
/**
* Programmatically closes the most recently opened context menu, if showing.
*/
public void closeContextMenu() {
mWindow.closePanel(Window.FEATURE_CONTEXT_MENU);
}
-
+
/**
* This hook is called whenever an item in a context menu is selected. The
* default implementation simply returns false to have the normal processing
@@ -3224,7 +3227,7 @@
* <p>
* Derived classes should call through to the base class for it to perform
* the default menu handling.
- *
+ *
* @param item The context menu item that was selected.
* @return boolean Return false to allow normal context menu processing to
* proceed, true to consume it here.
@@ -3240,7 +3243,7 @@
* This hook is called whenever the context menu is being closed (either by
* the user canceling the menu with the back/menu button, or when an item is
* selected).
- *
+ *
* @param menu The context menu that is being closed.
*/
public void onContextMenuClosed(Menu menu) {
@@ -3309,14 +3312,14 @@
* Provides an opportunity to prepare a managed dialog before it is being
* shown. The default implementation calls through to
* {@link #onPrepareDialog(int, Dialog)} for compatibility.
- *
+ *
* <p>
* Override this if you need to update a managed dialog based on the state
* of the application each time it is shown. For example, a time picker
* dialog might want to be updated with the current time. You should call
* through to the superclass's implementation. The default implementation
* will set this Activity as the owner activity on the Dialog.
- *
+ *
* @param id The id of the managed dialog.
* @param dialog The dialog.
* @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
@@ -3367,7 +3370,7 @@
* If you need to rebuild the dialog, call {@link #removeDialog(int)} first.
* @return Returns true if the Dialog was created; false is returned if
* it is not created because {@link #onCreateDialog(int, Bundle)} returns false.
- *
+ *
* @see Dialog
* @see #onCreateDialog(int, Bundle)
* @see #onPrepareDialog(int, Dialog, Bundle)
@@ -3393,7 +3396,7 @@
}
mManagedDialogs.put(id, md);
}
-
+
md.mArgs = args;
onPrepareDialog(id, md.mDialog, args);
md.mDialog.show();
@@ -3422,7 +3425,7 @@
if (mManagedDialogs == null) {
throw missingDialog(id);
}
-
+
final ManagedDialog md = mManagedDialogs.get(id);
if (md == null) {
throw missingDialog(id);
@@ -3449,7 +3452,7 @@
* <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, this function
* will not throw an exception if you try to remove an ID that does not
* currently have an associated dialog.</p>
- *
+ *
* @param id The id of the managed dialog.
*
* @see #onCreateDialog(int, Bundle)
@@ -3474,37 +3477,37 @@
/**
* This hook is called when the user signals the desire to start a search.
- *
+ *
* <p>You can use this function as a simple way to launch the search UI, in response to a
- * menu item, search button, or other widgets within your activity. Unless overidden,
+ * menu item, search button, or other widgets within your activity. Unless overidden,
* calling this function is the same as calling
* {@link #startSearch startSearch(null, false, null, false)}, which launches
* search for the current activity as specified in its manifest, see {@link SearchManager}.
- *
+ *
* <p>You can override this function to force global search, e.g. in response to a dedicated
* search key, or to block search entirely (by simply returning false).
- *
+ *
* @return Returns {@code true} if search launched, and {@code false} if activity blocks it.
* The default implementation always returns {@code true}.
- *
+ *
* @see android.app.SearchManager
*/
public boolean onSearchRequested() {
- startSearch(null, false, null, false);
+ startSearch(null, false, null, false);
return true;
}
-
+
/**
* This hook is called to launch the search UI.
- *
- * <p>It is typically called from onSearchRequested(), either directly from
- * Activity.onSearchRequested() or from an overridden version in any given
+ *
+ * <p>It is typically called from onSearchRequested(), either directly from
+ * Activity.onSearchRequested() or from an overridden version in any given
* Activity. If your goal is simply to activate search, it is preferred to call
* onSearchRequested(), which may have been overridden elsewhere in your Activity. If your goal
* is to inject specific data such as context data, it is preferred to <i>override</i>
* onSearchRequested(), so that any callers to it will benefit from the override.
- *
- * @param initialQuery Any non-null non-empty string will be inserted as
+ *
+ * @param initialQuery Any non-null non-empty string will be inserted as
* pre-entered text in the search query box.
* @param selectInitialQuery If true, the initial query will be preselected, which means that
* any further typing will replace it. This is useful for cases where an entire pre-formed
@@ -3512,15 +3515,15 @@
* inserted query. This is useful when the inserted query is text that the user entered,
* and the user would expect to be able to keep typing. <i>This parameter is only meaningful
* if initialQuery is a non-empty string.</i>
- * @param appSearchData An application can insert application-specific
- * context here, in order to improve quality or specificity of its own
+ * @param appSearchData An application can insert application-specific
+ * context here, in order to improve quality or specificity of its own
* searches. This data will be returned with SEARCH intent(s). Null if
* no extra data is required.
* @param globalSearch If false, this will only launch the search that has been specifically
- * defined by the application (which is usually defined as a local search). If no default
+ * defined by the application (which is usually defined as a local search). If no default
* search is defined in the current application or activity, global search will be launched.
* If true, this will always launch a platform-global (e.g. web-based) search instead.
- *
+ *
* @see android.app.SearchManager
* @see #onSearchRequested
*/
@@ -3528,7 +3531,7 @@
@Nullable Bundle appSearchData, boolean globalSearch) {
ensureSearchManager();
mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
- appSearchData, globalSearch);
+ appSearchData, globalSearch);
}
/**
@@ -3550,7 +3553,7 @@
* Request that key events come to this activity. Use this if your
* activity has no views with focus, but the activity still wants
* a chance to process key events.
- *
+ *
* @see android.view.Window#takeKeyEvents
*/
public void takeKeyEvents(boolean get) {
@@ -3560,12 +3563,12 @@
/**
* Enable extended window features. This is a convenience for calling
* {@link android.view.Window#requestFeature getWindow().requestFeature()}.
- *
+ *
* @param featureId The desired feature as defined in
* {@link android.view.Window}.
* @return Returns true if the requested feature is supported and now
* enabled.
- *
+ *
* @see android.view.Window#requestFeature
*/
public final boolean requestWindowFeature(int featureId) {
@@ -3677,7 +3680,7 @@
* Launch an activity for which you would like a result when it finished.
* When this activity exits, your
* onActivityResult() method will be called with the given requestCode.
- * Using a negative requestCode is the same as calling
+ * Using a negative requestCode is the same as calling
* {@link #startActivity} (the activity is not launched as a sub-activity).
*
* <p>Note that this method should only be used with Intent protocols
@@ -3687,7 +3690,7 @@
* are launching uses the singleTask launch mode, it will not run in your
* task and thus you will immediately receive a cancel result.
*
- * <p>As a special case, if you call startActivityForResult() with a requestCode
+ * <p>As a special case, if you call startActivityForResult() with a requestCode
* >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
* activity, then your window will not be displayed until a result is
* returned back from the started activity. This is to avoid visible
@@ -3845,7 +3848,7 @@
* here; otherwise, its associated action will be executed (such as
* sending a broadcast) as if you had called
* {@link IntentSender#sendIntent IntentSender.sendIntent} on it.
- *
+ *
* @param intent The IntentSender to launch.
* @param requestCode If >= 0, this code will be returned in
* onActivityResult() when the activity exits.
@@ -3936,19 +3939,19 @@
* information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not
* required; if not specified, the new activity will be added to the
* task of the caller.
- *
+ *
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
- *
- * @param intent The intent to start.
+ *
+ * @param intent The intent to start.
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)
* Context.startActivity(Intent, Bundle)} for more details.
- *
+ *
* @throws android.content.ActivityNotFoundException
*
* @see {@link #startActivity(Intent)}
- * @see #startActivityForResult
+ * @see #startActivityForResult
*/
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
@@ -4008,7 +4011,7 @@
/**
* Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
* with no options.
- *
+ *
* @param intent The IntentSender to launch.
* @param fillInIntent If non-null, this will be provided as the
* intent parameter to {@link IntentSender#sendIntent}.
@@ -4081,19 +4084,19 @@
/**
* A special variation to launch an activity only if a new activity
* instance is needed to handle the given Intent. In other words, this is
- * just like {@link #startActivityForResult(Intent, int)} except: if you are
+ * just like {@link #startActivityForResult(Intent, int)} except: if you are
* using the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag, or
- * singleTask or singleTop
+ * singleTask or singleTop
* {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode},
- * and the activity
- * that handles <var>intent</var> is the same as your currently running
- * activity, then a new instance is not needed. In this case, instead of
- * the normal behavior of calling {@link #onNewIntent} this function will
- * return and you can handle the Intent yourself.
- *
+ * and the activity
+ * that handles <var>intent</var> is the same as your currently running
+ * activity, then a new instance is not needed. In this case, instead of
+ * the normal behavior of calling {@link #onNewIntent} this function will
+ * return and you can handle the Intent yourself.
+ *
* <p>This function can only be called from a top-level activity; if it is
* called from a child activity, a runtime exception will be thrown.
- *
+ *
* @param intent The intent to start.
* @param requestCode If >= 0, this code will be returned in
* onActivityResult() when the activity exits, as described in
@@ -4101,10 +4104,10 @@
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)
* Context.startActivity(Intent, Bundle)} for more details.
- *
+ *
* @return If a new activity was launched then true is returned; otherwise
* false is returned and you must handle the Intent yourself.
- *
+ *
* @see #startActivity
* @see #startActivityForResult
*/
@@ -4167,7 +4170,7 @@
* other activity components. You can use this to hand the Intent off
* to the next Activity that can handle it. You typically call this in
* {@link #onCreate} with the Intent returned by {@link #getIntent}.
- *
+ *
* @param intent The intent to dispatch to the next activity. For
* correct behavior, this must be the same as the Intent that started
* your own activity; the only changes you can make are to the extras
@@ -4175,7 +4178,7 @@
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)
* Context.startActivity(Intent, Bundle)} for more details.
- *
+ *
* @return Returns a boolean indicating whether there was another Activity
* to start: true if there was a next activity to start, false if there
* wasn't. In general, if true is returned you will then want to call
@@ -4217,23 +4220,23 @@
}
/**
- * This is called when a child activity of this one calls its
+ * This is called when a child activity of this one calls its
* {@link #startActivity} or {@link #startActivityForResult} method.
- *
+ *
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
- *
+ *
* @param child The activity making the call.
* @param intent The intent to start.
* @param requestCode Reply request code. < 0 if reply is not requested.
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)
* Context.startActivity(Intent, Bundle)} for more details.
- *
+ *
* @throws android.content.ActivityNotFoundException
- *
- * @see #startActivity
- * @see #startActivityForResult
+ *
+ * @see #startActivity
+ * @see #startActivityForResult
*/
public void startActivityFromChild(@NonNull Activity child, Intent intent,
int requestCode, @Nullable Bundle options) {
@@ -4267,24 +4270,24 @@
}
/**
- * This is called when a Fragment in this activity calls its
+ * This is called when a Fragment in this activity calls its
* {@link Fragment#startActivity} or {@link Fragment#startActivityForResult}
* method.
- *
+ *
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
- *
+ *
* @param fragment The fragment making the call.
* @param intent The intent to start.
- * @param requestCode Reply request code. < 0 if reply is not requested.
+ * @param requestCode Reply request code. < 0 if reply is not requested.
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)
* Context.startActivity(Intent, Bundle)} for more details.
- *
+ *
* @throws android.content.ActivityNotFoundException
- *
- * @see Fragment#startActivity
- * @see Fragment#startActivityForResult
+ *
+ * @see Fragment#startActivity
+ * @see Fragment#startActivityForResult
*/
public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
int requestCode, @Nullable Bundle options) {
@@ -4352,14 +4355,14 @@
} catch (RemoteException e) {
}
}
-
+
/**
* Call this to set the result that your activity will return to its
* caller.
- *
+ *
* @param resultCode The result code to propagate back to the originating
* activity, often RESULT_CANCELED or RESULT_OK
- *
+ *
* @see #RESULT_CANCELED
* @see #RESULT_OK
* @see #RESULT_FIRST_USER
@@ -4388,7 +4391,7 @@
* @param resultCode The result code to propagate back to the originating
* activity, often RESULT_CANCELED or RESULT_OK
* @param data The data to propagate back to the originating activity.
- *
+ *
* @see #RESULT_CANCELED
* @see #RESULT_OK
* @see #RESULT_FIRST_USER
@@ -4406,10 +4409,10 @@
* the data in {@link #setResult setResult()} will be sent to. You can
* use this information to validate that the recipient is allowed to
* receive the data.
- *
+ *
* <p class="note">Note: if the calling activity is not expecting a result (that is it
- * did not use the {@link #startActivityForResult}
- * form that includes a request code), then the calling package will be
+ * did not use the {@link #startActivityForResult}
+ * form that includes a request code), then the calling package will be
* null.</p>
*
* <p class="note">Note: prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
@@ -4417,7 +4420,7 @@
* package was no longer running, it would return null instead of the proper package
* name. You can use {@link #getCallingActivity()} and retrieve the package name
* from that instead.</p>
- *
+ *
* @return The package of the activity that will receive your
* reply, or null if none.
*/
@@ -4435,12 +4438,12 @@
* who the data in {@link #setResult setResult()} will be sent to. You
* can use this information to validate that the recipient is allowed to
* receive the data.
- *
+ *
* <p class="note">Note: if the calling activity is not expecting a result (that is it
- * did not use the {@link #startActivityForResult}
- * form that includes a request code), then the calling package will be
- * null.
- *
+ * did not use the {@link #startActivityForResult}
+ * form that includes a request code), then the calling package will be
+ * null.
+ *
* @return The ComponentName of the activity that will receive your
* reply, or null if none.
*/
@@ -4459,7 +4462,7 @@
* UI itself, but can't just finish prior to onResume() because it needs
* to wait for a service binding or such. Setting this to false allows
* you to prevent your UI from being shown during that time.
- *
+ *
* <p>The default value for this is taken from the
* {@link android.R.attr#windowNoDisplay} attribute of the activity's theme.
*/
@@ -4472,7 +4475,7 @@
}
}
}
-
+
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
@@ -4481,16 +4484,16 @@
}
mDecor.setVisibility(View.VISIBLE);
}
-
+
/**
* Check to see whether this activity is in the process of finishing,
* either because you called {@link #finish} on it or someone else
* has requested that it finished. This is often used in
* {@link #onPause} to determine whether the activity is simply pausing or
* completely finishing.
- *
+ *
* @return If the activity is finishing, returns true; else returns false.
- *
+ *
* @see #finish
*/
public boolean isFinishing() {
@@ -4510,7 +4513,7 @@
* recreated with a new configuration. This is often used in
* {@link #onStop} to determine whether the state needs to be cleaned up or will be passed
* on to the next instance of the activity via {@link #onRetainNonConfigurationInstance()}.
- *
+ *
* @return If the activity is being torn down in order to be recreated with a new configuration,
* returns true; else returns false.
*/
@@ -4603,12 +4606,12 @@
}
/**
- * This is called when a child activity of this one calls its
+ * This is called when a child activity of this one calls its
* {@link #finish} method. The default implementation simply calls
* finish() on this activity (the parent), finishing the entire group.
- *
+ *
* @param child The activity making the call.
- *
+ *
* @see #finish
*/
public void finishFromChild(Activity child) {
@@ -4631,7 +4634,7 @@
/**
* Force finish another activity that you had previously started with
* {@link #startActivityForResult}.
- *
+ *
* @param requestCode The request code of the activity that you had
* given to startActivityForResult(). If there are multiple
* activities started with this request code, they
@@ -4653,7 +4656,7 @@
/**
* This is called when a child activity of this one calls its
* finishActivity().
- *
+ *
* @param child The activity making the call.
* @param requestCode Request code that had been used to start the
* activity.
@@ -4681,10 +4684,10 @@
* data from it. The <var>resultCode</var> will be
* {@link #RESULT_CANCELED} if the activity explicitly returned that,
* didn't return any result, or crashed during its operation.
- *
+ *
* <p>You will receive this call immediately before onResume() when your
* activity is re-starting.
- *
+ *
* @param requestCode The integer request code originally supplied to
* startActivityForResult(), allowing you to identify who this
* result came from.
@@ -4692,7 +4695,7 @@
* through its setResult().
* @param data An Intent, which can return result data to the caller
* (various data can be attached to Intent "extras").
- *
+ *
* @see #startActivityForResult
* @see #createPendingResult
* @see #setResult(int)
@@ -4722,12 +4725,12 @@
}
/**
- * Create a new PendingIntent object which you can hand to others
- * for them to use to send result data back to your
- * {@link #onActivityResult} callback. The created object will be either
- * one-shot (becoming invalid after a result is sent back) or multiple
- * (allowing any number of results to be sent through it).
- *
+ * Create a new PendingIntent object which you can hand to others
+ * for them to use to send result data back to your
+ * {@link #onActivityResult} callback. The created object will be either
+ * one-shot (becoming invalid after a result is sent back) or multiple
+ * (allowing any number of results to be sent through it).
+ *
* @param requestCode Private request code for the sender that will be
* associated with the result data when it is returned. The sender can not
* modify this value, allowing you to identify incoming results.
@@ -4740,12 +4743,12 @@
* or any of the flags as supported by
* {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
* of the intent that can be supplied when the actual send happens.
- *
+ *
* @return Returns an existing or new PendingIntent matching the given
* parameters. May return null only if
* {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE} has been
* supplied.
- *
+ *
* @see PendingIntent
*/
public PendingIntent createPendingResult(int requestCode, @NonNull Intent data,
@@ -4772,7 +4775,7 @@
* orientation, the screen will immediately be changed (possibly causing
* the activity to be restarted). Otherwise, this will be used the next
* time the activity is visible.
- *
+ *
* @param requestedOrientation An orientation constant as used in
* {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
*/
@@ -4788,13 +4791,13 @@
mParent.setRequestedOrientation(requestedOrientation);
}
}
-
+
/**
* Return the current requested orientation of the activity. This will
* either be the orientation requested in its component's manifest, or
* the last requested orientation given to
* {@link #setRequestedOrientation(int)}.
- *
+ *
* @return Returns an orientation constant as used in
* {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
*/
@@ -4812,11 +4815,11 @@
}
return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
-
+
/**
* Return the identifier of the task this activity is in. This identifier
* will remain the same for the lifetime of the activity.
- *
+ *
* @return Task identifier, an opaque integer.
*/
public int getTaskId() {
@@ -4831,7 +4834,7 @@
/**
* Return whether this activity is the root of a task. The root is the
* first activity in a task.
- *
+ *
* @return True if this is the root activity, else false.
*/
public boolean isTaskRoot() {
@@ -4846,11 +4849,11 @@
/**
* Move the task containing this activity to the back of the activity
* stack. The activity's order within the task is unchanged.
- *
+ *
* @param nonRoot If false then this only works if the activity is the root
* of a task; if true it will work for any activity in
* a task.
- *
+ *
* @return If the task was moved (or it was already at the
* back) true is returned, else false.
*/
@@ -4867,7 +4870,7 @@
/**
* Returns class name for this activity with the package prefix removed.
* This is the default name used to read and write settings.
- *
+ *
* @return The local class name.
*/
@NonNull
@@ -4881,10 +4884,10 @@
}
return cls.substring(packageLen+1);
}
-
+
/**
* Returns complete component name of this activity.
- *
+ *
* @return Returns the complete component name for this activity
*/
public ComponentName getComponentName()
@@ -4897,9 +4900,9 @@
* that are private to this activity. This simply calls the underlying
* {@link #getSharedPreferences(String, int)} method by passing in this activity's
* class name as the preferences name.
- *
- * @param mode Operating mode. Use {@link #MODE_PRIVATE} for the default
- * operation, {@link #MODE_WORLD_READABLE} and
+ *
+ * @param mode Operating mode. Use {@link #MODE_PRIVATE} for the default
+ * operation, {@link #MODE_WORLD_READABLE} and
* {@link #MODE_WORLD_WRITEABLE} to control permissions.
*
* @return Returns the single SharedPreferences instance that can be used
@@ -4908,12 +4911,12 @@
public SharedPreferences getPreferences(int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
-
+
private void ensureSearchManager() {
if (mSearchManager != null) {
return;
}
-
+
mSearchManager = new SearchManager(this, null);
}
@@ -5031,7 +5034,7 @@
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
- *
+ *
* @param visible Whether to show the progress bars in the title.
*/
public final void setProgressBarVisibility(boolean visible) {
@@ -5051,14 +5054,14 @@
getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF);
}
-
+
/**
* Sets whether the horizontal progress bar in the title should be indeterminate (the circular
* is always indeterminate).
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
- *
+ *
* @param indeterminate Whether the horizontal progress bar should be indeterminate.
*/
public final void setProgressBarIndeterminate(boolean indeterminate) {
@@ -5066,13 +5069,13 @@
indeterminate ? Window.PROGRESS_INDETERMINATE_ON
: Window.PROGRESS_INDETERMINATE_OFF);
}
-
+
/**
* Sets the progress for the progress bars in the title.
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
- *
+ *
* @param progress The progress for the progress bar. Valid ranges are from
* 0 to 10000 (both inclusive). If 10000 is given, the progress
* bar will be completely filled and will fade out.
@@ -5080,7 +5083,7 @@
public final void setProgress(int progress) {
getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START);
}
-
+
/**
* Sets the secondary progress for the progress bar in the title. This
* progress is drawn between the primary progress (set via
@@ -5090,7 +5093,7 @@
* <p>
* In order for the progress bar to be shown, the feature must be requested
* via {@link #requestWindowFeature(int)}.
- *
+ *
* @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from
* 0 to 10000 (both inclusive).
*/
@@ -5104,16 +5107,16 @@
* volume controls.
* <p>
* The suggested audio stream will be tied to the window of this Activity.
- * If the Activity is switched, the stream set here is no longer the
- * suggested stream. The client does not need to save and restore the old
- * suggested stream value in onPause and onResume.
- *
+ * Volume requests which are received while the Activity is in the
+ * foreground will affect this stream.
+ * <p>
+ * It is not guaranteed that the hardware volume controls will always change
+ * this stream's volume (for example, if a call is in progress, its stream's
+ * volume may be changed instead). To reset back to the default, use
+ * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
+ *
* @param streamType The type of the audio stream whose volume should be
- * changed by the hardware volume controls. It is not guaranteed that
- * the hardware volume controls will always change this stream's
- * volume (for example, if a call is in progress, its stream's volume
- * may be changed instead). To reset back to the default, use
- * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
+ * changed by the hardware volume controls.
*/
public final void setVolumeControlStream(int streamType) {
getWindow().setVolumeControlStream(streamType);
@@ -5122,7 +5125,7 @@
/**
* Gets the suggested audio stream whose volume should be changed by the
* hardware volume controls.
- *
+ *
* @return The suggested audio stream type whose volume should be changed by
* the hardware volume controls.
* @see #setVolumeControlStream(int)
@@ -5130,7 +5133,40 @@
public final int getVolumeControlStream() {
return getWindow().getVolumeControlStream();
}
-
+
+ /**
+ * Sets a {@link MediaController} to send media keys and volume changes to.
+ * <p>
+ * The controller will be tied to the window of this Activity. Media key and
+ * volume events which are received while the Activity is in the foreground
+ * will be forwarded to the controller and used to invoke transport controls
+ * or adjust the volume. This may be used instead of or in addition to
+ * {@link #setVolumeControlStream} to affect a specific session instead of a
+ * specific stream.
+ * <p>
+ * It is not guaranteed that the hardware volume controls will always change
+ * this session's volume (for example, if a call is in progress, its
+ * stream's volume may be changed instead). To reset back to the default use
+ * null as the controller.
+ *
+ * @param controller The controller for the session which should receive
+ * media keys and volume changes.
+ */
+ public final void setMediaController(MediaController controller) {
+ getWindow().setMediaController(controller);
+ }
+
+ /**
+ * Gets the controller which should be receiving media key and volume events
+ * while this activity is in the foreground.
+ *
+ * @return The controller which should receive events.
+ * @see #setMediaController(android.media.session.MediaController)
+ */
+ public final MediaController getMediaController() {
+ return getWindow().getMediaController();
+ }
+
/**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
@@ -5176,7 +5212,7 @@
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
-
+
return mFragments.onCreateView(parent, name, context, attrs);
}
@@ -5737,7 +5773,7 @@
}
// ------------------ Internal API ------------------
-
+
final void setParent(Activity parent) {
mParent = parent;
}
@@ -5751,7 +5787,7 @@
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
-
+
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
@@ -5846,7 +5882,7 @@
}
mActivityTransitionState.enterReady(this);
}
-
+
final void performRestart() {
mFragments.noteStateNotSaved();
@@ -5885,14 +5921,14 @@
performStart();
}
}
-
+
final void performResume() {
performRestart();
-
+
mFragments.execPendingActions();
-
+
mLastNonConfigurationInstances = null;
-
+
mCalled = false;
// mResumed is set by the instrumentation
mInstrumentation.callActivityOnResume(this);
@@ -5904,10 +5940,10 @@
// Now really resume, and install the current status bar and menu.
mCalled = false;
-
+
mFragments.dispatchResume();
mFragments.execPendingActions();
-
+
onPostResume();
if (!mCalled) {
throw new SuperNotCalledException(
@@ -5930,12 +5966,12 @@
}
mResumed = false;
}
-
+
final void performUserLeaving() {
onUserInteraction();
onUserLeaveHint();
}
-
+
final void performStop() {
mDoReportFullyDrawn = false;
if (mLoadersStarted) {
@@ -5948,7 +5984,7 @@
}
}
}
-
+
if (!mStopped) {
if (mWindow != null) {
mWindow.closeAllPanels();
@@ -5957,9 +5993,9 @@
if (mToken != null && mParent == null) {
WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
}
-
+
mFragments.dispatchStop();
-
+
mCalled = false;
mInstrumentation.callActivityOnStop(this);
if (!mCalled) {
@@ -5967,7 +6003,7 @@
"Activity " + mComponent.toShortString() +
" did not call through to super.onStop()");
}
-
+
synchronized (mManagedCursors) {
final int N = mManagedCursors.size();
for (int i=0; i<N; i++) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e1d0b86..c8cab6f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2456,7 +2456,10 @@
}
/**
- * @hide
+ * Return whether currently in lock task mode. When in this mode
+ * no new tasks can be created or switched to.
+ *
+ * @see Activity#startLockTask()
*/
public boolean isInLockTaskMode() {
try {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4a70e15..e2b5a84 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -30,7 +30,6 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
-import android.graphics.Bitmap;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
@@ -2196,13 +2195,21 @@
return true;
}
- case MEDIA_RESOURCES_RELEASED: {
+ case MEDIA_RESOURCES_RELEASED_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
mediaResourcesReleased(token);
reply.writeNoException();
return true;
}
+
+ case NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ notifyLaunchTaskBehindComplete(token);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -5069,7 +5076,20 @@
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
- mRemote.transact(MEDIA_RESOURCES_RELEASED, data, reply, IBinder.FLAG_ONEWAY);
+ mRemote.transact(MEDIA_RESOURCES_RELEASED_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION, data, reply,
+ IBinder.FLAG_ONEWAY);
reply.readException();
data.recycle();
reply.recycle();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index fafa948..4c02314 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -126,6 +126,8 @@
public static final int ANIM_SCENE_TRANSITION = 5;
/** @hide */
public static final int ANIM_DEFAULT = 6;
+ /** @hide */
+ public static final int ANIM_LAUNCH_TASK_BEHIND = 7;
private String mPackageName;
private int mAnimationType = ANIM_NONE;
@@ -432,6 +434,27 @@
return opts;
}
+ /**
+ * If set along with Intent.FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
+ * presented to the user but will instead be only available through the recents task list.
+ * In addition, the new task wil be affiliated with the launching activity's task.
+ * Affiliated tasks are grouped together in the recents task list.
+ *
+ * <p>This behavior is not supported for activities with {@link
+ * android.R.styleable#AndroidManifestActivity_launchMode launchMode} values of
+ * <code>singleInstance</code> or <code>singleTask</code>.
+ */
+ public static ActivityOptions makeLaunchTaskBehindAnimation() {
+ final ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_LAUNCH_TASK_BEHIND;
+ return opts;
+ }
+
+ /** @hide */
+ public boolean getLaunchTaskBehind() {
+ return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
+ }
+
private ActivityOptions() {
}
@@ -647,16 +670,15 @@
if (mPackageName != null) {
b.putString(KEY_PACKAGE_NAME, mPackageName);
}
+ b.putInt(KEY_ANIM_TYPE, mAnimationType);
switch (mAnimationType) {
case ANIM_CUSTOM:
- b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
case ANIM_SCALE_UP:
- b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putInt(KEY_ANIM_START_X, mStartX);
b.putInt(KEY_ANIM_START_Y, mStartY);
b.putInt(KEY_ANIM_START_WIDTH, mStartWidth);
@@ -664,7 +686,6 @@
break;
case ANIM_THUMBNAIL_SCALE_UP:
case ANIM_THUMBNAIL_SCALE_DOWN:
- b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
b.putInt(KEY_ANIM_START_X, mStartX);
b.putInt(KEY_ANIM_START_Y, mStartY);
@@ -672,7 +693,6 @@
!= null ? mAnimationStartedListener.asBinder() : null);
break;
case ANIM_SCENE_TRANSITION:
- b.putInt(KEY_ANIM_TYPE, mAnimationType);
if (mTransitionReceiver != null) {
b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
}
@@ -683,6 +703,7 @@
b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
break;
}
+
return b;
}
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 15be9b1..e074219 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -403,20 +403,18 @@
view.layout(left, top, right, bottom);
}
- protected ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
+ protected ArrayList<SharedElementOriginalState> setSharedElementState(
Bundle sharedElementState, final ArrayList<View> snapshots) {
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
- new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
+ ArrayList<SharedElementOriginalState> originalImageState =
+ new ArrayList<SharedElementOriginalState>();
if (sharedElementState != null) {
int[] tempLoc = new int[2];
for (int i = 0; i < mSharedElementNames.size(); i++) {
View sharedElement = mSharedElements.get(i);
String name = mSharedElementNames.get(i);
- Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
+ SharedElementOriginalState originalState = getOldSharedElementState(sharedElement,
name, sharedElementState);
- if (originalState != null) {
- originalImageState.put((ImageView) sharedElement, originalState);
- }
+ originalImageState.add(originalState);
View parent = (View) sharedElement.getParent();
parent.getLocationOnScreen(tempLoc);
setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
@@ -438,29 +436,34 @@
return originalImageState;
}
- private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name,
+ private static SharedElementOriginalState getOldSharedElementState(View view, String name,
Bundle transitionArgs) {
+
+ SharedElementOriginalState state = new SharedElementOriginalState();
+ state.mLeft = view.getLeft();
+ state.mTop = view.getTop();
+ state.mRight = view.getRight();
+ state.mBottom = view.getBottom();
+ state.mMeasuredWidth = view.getMeasuredWidth();
+ state.mMeasuredHeight = view.getMeasuredHeight();
if (!(view instanceof ImageView)) {
- return null;
+ return state;
}
Bundle bundle = transitionArgs.getBundle(name);
if (bundle == null) {
- return null;
+ return state;
}
int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
if (scaleTypeInt < 0) {
- return null;
+ return state;
}
ImageView imageView = (ImageView) view;
- ImageView.ScaleType originalScaleType = imageView.getScaleType();
-
- Matrix originalMatrix = null;
- if (originalScaleType == ImageView.ScaleType.MATRIX) {
- originalMatrix = new Matrix(imageView.getImageMatrix());
+ state.mScaleType = imageView.getScaleType();
+ if (state.mScaleType == ImageView.ScaleType.MATRIX) {
+ state.mMatrix = new Matrix(imageView.getImageMatrix());
}
-
- return Pair.create(originalScaleType, originalMatrix);
+ return state;
}
protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
@@ -489,13 +492,26 @@
return snapshots;
}
- protected static void setOriginalImageViewState(
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
+ protected static void setOriginalSharedElementState(ArrayList<View> sharedElements,
+ ArrayList<SharedElementOriginalState> originalState) {
for (int i = 0; i < originalState.size(); i++) {
- ImageView imageView = originalState.keyAt(i);
- Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
- imageView.setScaleType(state.first);
- imageView.setImageMatrix(state.second);
+ View view = sharedElements.get(i);
+ SharedElementOriginalState state = originalState.get(i);
+ if (view instanceof ImageView && state.mScaleType != null) {
+ ImageView imageView = (ImageView) view;
+ imageView.setScaleType(state.mScaleType);
+ if (state.mScaleType == ImageView.ScaleType.MATRIX) {
+ imageView.setImageMatrix(state.mMatrix);
+ }
+ }
+ // origignal widthspec might be AT_MOST, but it should work for most
+ // cases.
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth,
+ View.MeasureSpec.EXACTLY);
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight,
+ View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+ view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom);
}
}
@@ -622,4 +638,15 @@
}
}
+ static class SharedElementOriginalState {
+ int mLeft;
+ int mTop;
+ int mRight;
+ int mBottom;
+ int mMeasuredWidth;
+ int mMeasuredHeight;
+ ImageView.ScaleType mScaleType;
+ Matrix mMatrix;
+ }
+
}
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 0d2af8c..4f556a8 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -95,6 +95,8 @@
*/
private int mExitTransitionCoordinatorsKey = 1;
+ private boolean mIsEnterTriggered;
+
public ActivityTransitionState() {
}
@@ -142,8 +144,10 @@
public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
if (activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)
&& options != null && mEnterActivityOptions == null
+ && mEnterTransitionCoordinator == null
&& options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
mEnterActivityOptions = options;
+ mIsEnterTriggered = false;
if (mEnterActivityOptions.isReturning()) {
int result = mEnterActivityOptions.getResultCode();
if (result != 0) {
@@ -154,9 +158,10 @@
}
public void enterReady(Activity activity) {
- if (mEnterActivityOptions == null) {
+ if (mEnterActivityOptions == null || mIsEnterTriggered) {
return;
}
+ mIsEnterTriggered = true;
mHasExited = false;
ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
@@ -259,6 +264,7 @@
return;
}
ActivityOptions activityOptions = new ActivityOptions(options);
+ mEnterTransitionCoordinator = null;
if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
int key = activityOptions.getExitCoordinatorKey();
int index = mExitTransitionCoordinators.indexOfKey(key);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 2935b8e..264553b 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -33,6 +33,7 @@
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -52,6 +53,7 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -59,6 +61,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.view.Display;
+import com.android.internal.util.Preconditions;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -1447,6 +1450,62 @@
return false;
}
+ @Override
+ public KeySet getKeySetByAlias(String packageName, String alias) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(alias);
+ IBinder keySetToken;
+ try {
+ keySetToken = mPM.getKeySetByAlias(packageName, alias);
+ } catch (RemoteException e) {
+ return null;
+ }
+ if (keySetToken == null) {
+ return null;
+ }
+ return new KeySet(keySetToken);
+ }
+
+ @Override
+ public KeySet getSigningKeySet(String packageName) {
+ Preconditions.checkNotNull(packageName);
+ IBinder keySetToken;
+ try {
+ keySetToken = mPM.getSigningKeySet(packageName);
+ } catch (RemoteException e) {
+ return null;
+ }
+ if (keySetToken == null) {
+ return null;
+ }
+ return new KeySet(keySetToken);
+ }
+
+
+ @Override
+ public boolean isSignedBy(String packageName, KeySet ks) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(ks);
+ IBinder keySetToken = ks.getToken();
+ try {
+ return mPM.isPackageSignedByKeySet(packageName, keySetToken);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isSignedByExactly(String packageName, KeySet ks) {
+ Preconditions.checkNotNull(packageName);
+ Preconditions.checkNotNull(ks);
+ IBinder keySetToken = ks.getToken();
+ try {
+ return mPM.isPackageSignedByKeySetExactly(packageName, keySetToken);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
/**
* @hide
*/
@@ -1461,7 +1520,7 @@
}
@Override
- public PackageInstaller getPackageInstaller() {
+ public PackageInstaller getInstaller() {
try {
return new PackageInstaller(this, mPM.getPackageInstaller(), mContext.getPackageName(),
mContext.getUserId());
@@ -1470,6 +1529,15 @@
}
}
+ @Override
+ public boolean isPackageAvailable(String packageName) {
+ try {
+ return mPM.isPackageAvailable(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index bbfb05e..b9de220 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -247,6 +247,8 @@
@GuardedBy("mSync")
private File mFilesDir;
@GuardedBy("mSync")
+ private File mNoBackupFilesDir;
+ @GuardedBy("mSync")
private File mCacheDir;
@GuardedBy("mSync")
@@ -558,9 +560,7 @@
registerService(TELECOMM_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(TELECOMM_SERVICE);
- return new TelecommManager(ctx.getOuterContext(),
- ITelecommService.Stub.asInterface(b));
+ return new TelecommManager(ctx.getOuterContext());
}});
registerService(PHONE_SERVICE, new ServiceFetcher() {
@@ -963,27 +963,42 @@
return f.delete();
}
+ // Common-path handling of app data dir creation
+ private static File createFilesDirLocked(File file) {
+ if (!file.exists()) {
+ if (!file.mkdirs()) {
+ if (file.exists()) {
+ // spurious failure; probably racing with another process for this app
+ return file;
+ }
+ Log.w(TAG, "Unable to create files subdir " + file.getPath());
+ return null;
+ }
+ FileUtils.setPermissions(
+ file.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ }
+ return file;
+ }
+
@Override
public File getFilesDir() {
synchronized (mSync) {
if (mFilesDir == null) {
mFilesDir = new File(getDataDirFile(), "files");
}
- if (!mFilesDir.exists()) {
- if(!mFilesDir.mkdirs()) {
- if (mFilesDir.exists()) {
- // spurious failure; probably racing with another process for this app
- return mFilesDir;
- }
- Log.w(TAG, "Unable to create files directory " + mFilesDir.getPath());
- return null;
- }
- FileUtils.setPermissions(
- mFilesDir.getPath(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
+ return createFilesDirLocked(mFilesDir);
+ }
+ }
+
+ @Override
+ public File getNoBackupFilesDir() {
+ synchronized (mSync) {
+ if (mNoBackupFilesDir == null) {
+ mNoBackupFilesDir = new File(getDataDirFile(), "no_backup");
}
- return mFilesDir;
+ return createFilesDirLocked(mNoBackupFilesDir);
}
}
@@ -1035,22 +1050,8 @@
if (mCacheDir == null) {
mCacheDir = new File(getDataDirFile(), "cache");
}
- if (!mCacheDir.exists()) {
- if(!mCacheDir.mkdirs()) {
- if (mCacheDir.exists()) {
- // spurious failure; probably racing with another process for this app
- return mCacheDir;
- }
- Log.w(TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath());
- return null;
- }
- FileUtils.setPermissions(
- mCacheDir.getPath(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
- }
+ return createFilesDirLocked(mCacheDir);
}
- return mCacheDir;
}
@Override
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 26c2c30..d7fb707 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -21,6 +21,7 @@
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.text.format.DateUtils;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.DatePicker;
@@ -44,10 +45,11 @@
private static final String DAY = "day";
private final DatePicker mDatePicker;
- private final OnDateSetListener mCallBack;
+ private final OnDateSetListener mDateSetListener;
private final Calendar mCalendar;
private boolean mTitleNeedsUpdate = true;
+ private boolean mIsCanceled = false;
/**
* The callback used to indicate the user is done filling in the date.
@@ -79,29 +81,36 @@
this(context, 0, callBack, year, monthOfYear, dayOfMonth);
}
+ static int resolveDialogTheme(Context context, int resid) {
+ if (resid == 0) {
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.datePickerDialogTheme, outValue, true);
+ return outValue.resourceId;
+ } else {
+ return resid;
+ }
+ }
+
/**
* @param context The context the dialog is to run in.
* @param theme the theme to apply to this dialog
- * @param callBack How the parent is notified that the date is set.
+ * @param listener How the parent is notified that the date is set.
* @param year The initial year of the dialog.
* @param monthOfYear The initial month of the dialog.
* @param dayOfMonth The initial day of the dialog.
*/
public DatePickerDialog(Context context,
int theme,
- OnDateSetListener callBack,
+ OnDateSetListener listener,
int year,
int monthOfYear,
int dayOfMonth) {
- super(context, theme);
+ super(context, resolveDialogTheme(context, theme));
- mCallBack = callBack;
-
+ mDateSetListener = listener;
mCalendar = Calendar.getInstance();
Context themeContext = getContext();
- setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this);
- setIcon(0);
LayoutInflater inflater =
(LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -109,20 +118,53 @@
setView(view);
setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
+
+ // Initialize state
+ mDatePicker.setLegacyMode(false, null);
+ mDatePicker.setShowDoneButton(true);
+ mDatePicker.setDismissCallback(new DatePicker.DatePickerDismissCallback() {
+ @Override
+ public void dismiss(DatePicker view, boolean isCancel, int year, int month, int dayOfMonth) {
+ mIsCanceled = isCancel;
+ if (!isCancel) {
+ mDateSetListener.onDateSet(view, year, month, dayOfMonth);
+ }
+ DatePickerDialog.this.dismiss();
+ }
+ });
mDatePicker.init(year, monthOfYear, dayOfMonth, this);
- updateTitle(year, monthOfYear, dayOfMonth);
}
public void onClick(DialogInterface dialog, int which) {
tryNotifyDateSet();
}
+ @Override
+ public void cancel() {
+ mIsCanceled = true;
+ super.cancel();
+ }
+
+ @Override
+ protected void onStop() {
+ tryNotifyDateSet();
+ super.onStop();
+ }
+
public void onDateChanged(DatePicker view, int year,
int month, int day) {
mDatePicker.init(year, month, day, this);
updateTitle(year, month, day);
}
+ private void tryNotifyDateSet() {
+ if (mDateSetListener != null && !mIsCanceled) {
+ mDatePicker.clearFocus();
+ mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(),
+ mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
+ }
+ }
+
/**
* Gets the {@link DatePicker} contained in this dialog.
*
@@ -143,20 +185,6 @@
mDatePicker.updateDate(year, monthOfYear, dayOfMonth);
}
- private void tryNotifyDateSet() {
- if (mCallBack != null) {
- mDatePicker.clearFocus();
- mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(),
- mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
- }
- }
-
- @Override
- protected void onStop() {
- tryNotifyDateSet();
- super.onStop();
- }
-
private void updateTitle(int year, int month, int day) {
if (!mDatePicker.getCalendarViewShown()) {
mCalendar.set(Calendar.YEAR, year);
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index f50c93b..1326064 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -282,7 +282,7 @@
ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
mSharedElementNames);
setTransitionAlpha(mSharedElements, 1);
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
+ ArrayList<SharedElementOriginalState> originalImageViewState =
setSharedElementState(sharedElementState, sharedElementSnapshots);
requestLayoutForSharedElements();
@@ -294,7 +294,7 @@
startEnterTransition(transition);
}
- setOriginalImageViewState(originalImageViewState);
+ setOriginalSharedElementState(mSharedElements, originalImageViewState);
if (mResultReceiver != null) {
// We can't trust that the view will disappear on the same frame that the shared
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 3f3e00c..4f5a098 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -329,7 +329,7 @@
private Bundle captureExitSharedElementsState() {
Bundle bundle = new Bundle();
Rect bounds = new Rect();
- for (int i = 0; i < mSharedElementNames.size(); i++) {
+ for (int i = 0; i < mSharedElements.size(); i++) {
String name = mSharedElementNames.get(i);
Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
if (sharedElementState != null) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b3a8a8a..c6921a2 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -444,6 +444,8 @@
public boolean isBackgroundMediaPlaying(IBinder token) throws RemoteException;
public void mediaResourcesReleased(IBinder token) throws RemoteException;
+ public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -750,5 +752,6 @@
int IS_TOP_OF_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+224;
int SET_MEDIA_PLAYING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+225;
int IS_BG_MEDIA_PLAYING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+226;
- int MEDIA_RESOURCES_RELEASED = IBinder.FIRST_CALL_TRANSACTION+227;
+ int MEDIA_RESOURCES_RELEASED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+227;
+ int NOTIFY_LAUNCH_TASK_BEHIND_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+228;
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index a3ffc00..5b92538 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -44,6 +44,9 @@
void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg, int uid);
+ void setPackagePriority(String pkg, int uid, int priority);
+ int getPackagePriority(String pkg, int uid);
+
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
StatusBarNotification[] getActiveNotifications(String callingPkg);
diff --git a/core/java/android/app/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index 7117111..1b2504e 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -25,7 +25,7 @@
@Override
public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
String msg) {
- PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode);
+ PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode, msg);
}
};
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 886f1a6..e2a86e8 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -247,12 +247,40 @@
throws IOException;
/**
- * The default implementation backs up the entirety of the application's "owned"
- * file system trees to the output.
+ * The application is having its entire file system contents backed up. {@code data}
+ * points to the backup destination, and the app has the opportunity to choose which
+ * files are to be stored. To commit a file as part of the backup, call the
+ * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file
+ * data is written to the output, the agent returns from this method and the backup
+ * operation concludes.
+ *
+ * <p>Certain parts of the app's data are never backed up even if the app explicitly
+ * sends them to the output:
+ *
+ * <ul>
+ * <li>The contents of the {@link #getCacheDir()} directory</li>
+ * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
+ * <li>The contents of the app's shared library directory</li>
+ * </ul>
+ *
+ * <p>The default implementation of this method backs up the entirety of the
+ * application's "owned" file system trees to the output other than the few exceptions
+ * listed above. Apps only need to override this method if they need to impose special
+ * limitations on which files are being stored beyond the control that
+ * {@link #getNoBackupFilesDir()} offers.
+ *
+ * @param data A structured wrapper pointing to the backup destination.
+ * @throws IOException
+ *
+ * @see Context#getNoBackupFilesDir()
+ * @see #fullBackupFile(File, FullBackupDataOutput)
+ * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long)
*/
public void onFullBackup(FullBackupDataOutput data) throws IOException {
ApplicationInfo appInfo = getApplicationInfo();
+ // Note that we don't need to think about the no_backup dir because it's outside
+ // all of the ones we will be traversing
String rootDir = new File(appInfo.dataDir).getCanonicalPath();
String filesDir = getFilesDir().getCanonicalPath();
String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
@@ -311,6 +339,10 @@
* to place it with the proper location and permissions on the device where the
* data is restored.
*
+ * <p class="note">It is safe to explicitly back up files underneath your application's
+ * {@link #getNoBackupFilesDir()} directory, and they will be restored to that
+ * location correctly.
+ *
* @param file The file to be backed up. The file must exist and be readable by
* the caller.
* @param output The destination to which the backed-up file data will be sent.
@@ -319,6 +351,7 @@
// Look up where all of our various well-defined dir trees live on this device
String mainDir;
String filesDir;
+ String nbFilesDir;
String dbDir;
String spDir;
String cacheDir;
@@ -331,6 +364,7 @@
try {
mainDir = new File(appInfo.dataDir).getCanonicalPath();
filesDir = getFilesDir().getCanonicalPath();
+ nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
cacheDir = getCacheDir().getCanonicalPath();
@@ -354,8 +388,10 @@
return;
}
- if (filePath.startsWith(cacheDir) || filePath.startsWith(libDir)) {
- Log.w(TAG, "lib and cache files are not backed up");
+ if (filePath.startsWith(cacheDir)
+ || filePath.startsWith(libDir)
+ || filePath.startsWith(nbFilesDir)) {
+ Log.w(TAG, "lib, cache, and no_backup files are not backed up");
return;
}
@@ -508,6 +544,8 @@
mode = -1; // < 0 is a token to skip attempting a chmod()
}
}
+ } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
+ basePath = getNoBackupFilesDir().getCanonicalPath();
} else {
// Not a supported location
Log.i(TAG, "Unrecognized domain " + domain);
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 6ebb6c4..e5b47c6 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -40,6 +40,7 @@
public static final String OBB_TREE_TOKEN = "obb";
public static final String ROOT_TREE_TOKEN = "r";
public static final String DATA_TREE_TOKEN = "f";
+ public static final String NO_BACKUP_TREE_TOKEN = "nb";
public static final String DATABASE_TREE_TOKEN = "db";
public static final String SHAREDPREFS_TREE_TOKEN = "sp";
public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 97e3fc5..faf8645 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -219,22 +219,6 @@
"android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
/**
- * Broadcast Action: Indicate BLE Advertising is started.
- * @hide
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_BLUETOOTH_ADVERTISING_STARTED =
- "android.bluetooth.adapter.action.ADVERTISING_STARTED";
-
- /**
- * Broadcast Action: Indicated BLE Advertising is stopped.
- * @hide
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_BLUETOOTH_ADVERTISING_STOPPED =
- "android.bluetooth.adapter.action.ADVERTISING_STOPPED";
-
- /**
* Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED}
* intents to request the current scan mode. Possible values are:
* {@link #SCAN_MODE_NONE},
@@ -403,8 +387,6 @@
private IBluetooth mService;
private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients;
- private BluetoothAdvScanData mBluetoothAdvScanData = null;
- private GattCallbackWrapper mAdvertisingGattCallback;
private final Handler mHandler; // Handler to post the advertise callback to run on main thread.
private final Object mLock = new Object();
@@ -481,29 +463,6 @@
}
/**
- * Returns a {@link BluetoothAdvScanData} object representing advertising data.
- * Data will be reset when bluetooth service is turned off.
- * @hide
- */
- public BluetoothAdvScanData getAdvScanData() {
- try {
- IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
- if (iGatt == null) {
- // BLE is not supported
- Log.e(TAG, "failed to start, iGatt null");
- return null;
- }
- if (mBluetoothAdvScanData == null) {
- mBluetoothAdvScanData = new BluetoothAdvScanData(iGatt, BluetoothAdvScanData.AD);
- }
- return mBluetoothAdvScanData;
- } catch (RemoteException e) {
- Log.e(TAG, "failed to get advScanData, error: " + e);
- return null;
- }
- }
-
- /**
* Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations.
*/
public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
@@ -520,106 +479,6 @@
}
/**
- * Interface for BLE advertising callback.
- *
- * @hide
- */
- public interface AdvertiseCallback {
- /**
- * Callback when advertise starts.
- * @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure.
- */
- void onAdvertiseStart(int status);
- /**
- * Callback when advertise stops.
- * @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure.
- */
- void onAdvertiseStop(int status);
- }
-
- /**
- * Start BLE advertising using current {@link BluetoothAdvScanData}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
- *
- * @param callback - {@link AdvertiseCallback}
- * @return true if BLE advertising succeeds, false otherwise.
- * @hide
- */
- public boolean startAdvertising(final AdvertiseCallback callback) {
- if (getState() != STATE_ON) return false;
- try {
- IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
- if (iGatt == null) {
- // BLE is not supported.
- return false;
- }
- // Restart/reset advertising packets if advertising is in progress.
- if (isAdvertising()) {
- // Invalid advertising callback.
- if (mAdvertisingGattCallback == null || mAdvertisingGattCallback.mLeHandle == -1) {
- Log.e(TAG, "failed to restart advertising, invalid callback");
- return false;
- }
- iGatt.startAdvertising(mAdvertisingGattCallback.mLeHandle);
- // Run the callback from main thread.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- // callback with status success.
- callback.onAdvertiseStart(ADVERTISE_CALLBACK_SUCCESS);
- }
- });
- return true;
- }
- UUID uuid = UUID.randomUUID();
- GattCallbackWrapper wrapper =
- new GattCallbackWrapper(this, null, null, callback);
- iGatt.registerClient(new ParcelUuid(uuid), wrapper);
- if (!wrapper.advertiseStarted()) {
- return false;
- }
- synchronized (mLock) {
- mAdvertisingGattCallback = wrapper;
- }
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
- }
- }
-
- /**
- * Stop BLE advertising. The callback has to be the same one used for start advertising.
- *
- * @param callback - {@link AdvertiseCallback}
- * @return true if BLE advertising stops, false otherwise.
- * @hide
- */
- public boolean stopAdvertising(AdvertiseCallback callback) {
- try {
- IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
- if (iGatt == null) {
- // BLE is not supported
- return false;
- }
- if (mAdvertisingGattCallback == null) {
- // no callback.
- return false;
- }
- // Make sure same callback is used for start and stop advertising.
- if (callback != mAdvertisingGattCallback.mAdvertiseCallback) {
- Log.e(TAG, "must use the same callback for star/stop advertising");
- return false;
- }
- mAdvertisingGattCallback.stopAdvertising();
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
- }
- }
-
- /**
* Return true if Bluetooth is currently enabled and ready for use.
* <p>Equivalent to:
* <code>getBluetoothState() == STATE_ON</code>
@@ -1076,23 +935,6 @@
}
/**
- * Returns whether BLE is currently advertising.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
- *
- * @hide
- */
- public boolean isAdvertising() {
- if (getState() != STATE_ON) return false;
- try {
- IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
- return iGatt.isAdvertising();
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- }
- return false;
- }
-
- /**
* Return the set of {@link BluetoothDevice} objects that are bonded
* (paired) to the local adapter.
* <p>If Bluetooth state is not {@link #STATE_ON}, this API
@@ -1537,8 +1379,6 @@
if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
synchronized (mManagerCallback) {
mService = null;
- // Reset bluetooth adv scan data when Gatt service is down.
- mBluetoothAdvScanData = null;
for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
try {
if (cb != null) {
@@ -1822,7 +1662,6 @@
private static final int LE_CALLBACK_REG_TIMEOUT = 2000;
private static final int LE_CALLBACK_REG_WAIT_COUNT = 5;
- private final AdvertiseCallback mAdvertiseCallback;
private final LeScanCallback mLeScanCb;
// mLeHandle 0: not registered
@@ -1838,27 +1677,12 @@
mLeScanCb = leScanCb;
mScanFilter = uuid;
mLeHandle = 0;
- mAdvertiseCallback = null;
- }
-
- public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter, LeScanCallback leScanCb,
- UUID[] uuid, AdvertiseCallback callback) {
- mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter);
- mLeScanCb = leScanCb;
- mScanFilter = uuid;
- mLeHandle = 0;
- mAdvertiseCallback = callback;
}
public boolean scanStarted() {
return waitForRegisteration(LE_CALLBACK_REG_WAIT_COUNT);
}
- public boolean advertiseStarted() {
- // Wait for registeration callback.
- return waitForRegisteration(1);
- }
-
private boolean waitForRegisteration(int maxWaitCount) {
boolean started = false;
synchronized(this) {
@@ -1878,27 +1702,6 @@
return started;
}
- public void stopAdvertising() {
- synchronized (this) {
- if (mLeHandle <= 0) {
- Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
- return;
- }
- BluetoothAdapter adapter = mBluetoothAdapter.get();
- if (adapter != null) {
- try {
- IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt();
- iGatt.stopAdvertising();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to stop advertising" + e);
- }
- } else {
- Log.e(TAG, "stopAdvertising, BluetoothAdapter is null");
- }
- notifyAll();
- }
- }
-
public void stopLeScan() {
synchronized(this) {
if (mLeHandle <= 0) {
@@ -1940,18 +1743,14 @@
BluetoothAdapter adapter = mBluetoothAdapter.get();
if (adapter != null) {
iGatt = adapter.getBluetoothManager().getBluetoothGatt();
- if (mAdvertiseCallback != null) {
- iGatt.startAdvertising(mLeHandle);
+ if (mScanFilter == null) {
+ iGatt.startScan(mLeHandle, false);
} else {
- if (mScanFilter == null) {
- iGatt.startScan(mLeHandle, false);
- } else {
- ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
- for(int i = 0; i != uuids.length; ++i) {
- uuids[i] = new ParcelUuid(mScanFilter[i]);
- }
- iGatt.startScanWithUuids(mLeHandle, false, uuids);
- }
+ ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
+ for(int i = 0; i != uuids.length; ++i) {
+ uuids[i] = new ParcelUuid(mScanFilter[i]);
+ }
+ iGatt.startScanWithUuids(mLeHandle, false, uuids);
}
} else {
Log.e(TAG, "onClientRegistered, BluetoothAdapter null");
@@ -2080,44 +1879,6 @@
}
public void onAdvertiseStateChange(int advertiseState, int status) {
- Log.d(TAG, "on advertise call back, state: " + advertiseState + " status: " + status);
- if (advertiseState == STATE_ADVERTISE_STARTED) {
- if (status == ADVERTISE_CALLBACK_SUCCESS) {
- mAdvertiseCallback.onAdvertiseStart(status);
- } else {
- // If status is unsuccessful and advertise state is started, it means stop
- // advertising fails.
- mAdvertiseCallback.onAdvertiseStop(status);
- }
- } else {
- synchronized (this) {
- if (status == ADVERTISE_CALLBACK_SUCCESS) {
- BluetoothAdapter adapter = mBluetoothAdapter.get();
- if (adapter != null) {
- try {
- IBluetoothGatt iGatt =
- adapter.getBluetoothManager().getBluetoothGatt();
- Log.d(TAG, "unregistering client " + mLeHandle);
- iGatt.unregisterClient(mLeHandle);
- // Reset advertise app handle.
- mLeHandle = -1;
- adapter.mAdvertisingGattCallback = null;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to unregister client" + e);
- }
- } else {
- Log.e(TAG, "cannot unregister client, BluetoothAdapter is null");
- }
- }
- }
- if (status == ADVERTISE_CALLBACK_SUCCESS) {
- mAdvertiseCallback.onAdvertiseStop(status);
- } else{
- // If status is unsuccesful and advertise state is stopped, it means start
- // advertising fails.
- mAdvertiseCallback.onAdvertiseStart(status);
- }
- }
}
@Override
diff --git a/core/java/android/bluetooth/BluetoothAdvScanData.java b/core/java/android/bluetooth/BluetoothAdvScanData.java
deleted file mode 100644
index df2c256..0000000
--- a/core/java/android/bluetooth/BluetoothAdvScanData.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-import android.os.ParcelUuid;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * This class provides the public APIs to set advertising and scan response data when BLE device
- * operates in peripheral mode. <br>
- * The exact format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11
- * @hide
- */
-public final class BluetoothAdvScanData {
-
- /**
- * Available data types of {@link BluetoothAdvScanData}.
- */
- public static final int AD = 0; // Advertising Data
- public static final int SCAN_RESPONSE = 1; // Scan Response
- public static final int EIR = 2; // Extended Inquiry Response
-
- private static final String TAG = "BluetoothAdvScanData";
-
- /**
- * Data type of BluetoothAdvScanData.
- */
- private final int mDataType;
- /**
- * Bluetooth Gatt Service.
- */
- private IBluetoothGatt mBluetoothGatt;
-
- /**
- * @param mBluetoothGatt
- * @param dataType
- */
- public BluetoothAdvScanData(IBluetoothGatt mBluetoothGatt, int dataType) {
- this.mBluetoothGatt = mBluetoothGatt;
- this.mDataType = dataType;
- }
-
- /**
- * @return advertising data type.
- */
- public int getDataType() {
- return mDataType;
- }
-
- /**
- * Set manufactureCode and manufactureData.
- * Returns true if manufacturer data is set, false if there is no enough room to set
- * manufacturer data or the data is already set.
- * @param manufacturerCode - unique identifier for the manufacturer
- * @param manufacturerData - data associated with the specific manufacturer.
- */
- public boolean setManufacturerData(int manufacturerCode, byte[] manufacturerData) {
- if (mDataType != AD) return false;
- try {
- return mBluetoothGatt.setAdvManufacturerCodeAndData(manufacturerCode, manufacturerData);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to set manufacturer id and data.", e);
- return false;
- }
- }
-
- /**
- * Set service data. Note the service data can only be set when the data type is {@code AD};
- * @param serviceData
- */
- public boolean setServiceData(byte[] serviceData) {
-
- if (mDataType != AD) return false;
- if (serviceData == null) return false;
- try {
- return mBluetoothGatt.setAdvServiceData(serviceData);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to set service data.", e);
- return false;
- }
- }
-
- /**
- * Returns an immutable list of service uuids that will be advertised.
- */
- public List<ParcelUuid> getServiceUuids() {
- try {
- return Collections.unmodifiableList(mBluetoothGatt.getAdvServiceUuids());
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to get service uuids.", e);
- return null;
- }
- }
-
- /**
- * Returns manufacturer data.
- */
- public byte[] getManufacturerData() {
- if (mBluetoothGatt == null) return null;
- try {
- return mBluetoothGatt.getAdvManufacturerData();
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to get manufacturer data.", e);
- return null;
- }
- }
-
- /**
- * Returns service data.
- */
- public byte[] getServiceData() {
- if (mBluetoothGatt == null) return null;
- try {
- return mBluetoothGatt.getAdvServiceData();
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to get service data.", e);
- return null;
- }
- }
-
- /**
- * Remove manufacturer data based on given manufacturer code.
- * @param manufacturerCode
- */
- public void removeManufacturerCodeAndData(int manufacturerCode) {
- if (mBluetoothGatt != null) {
- try {
- mBluetoothGatt.removeAdvManufacturerCodeAndData(manufacturerCode);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to remove manufacturer : " + manufacturerCode, e);
- }
- }
- }
-}
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 0f0eee6..6d4b9cd 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -48,15 +48,6 @@
void unregisterClient(in int clientIf);
void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport);
void clientDisconnect(in int clientIf, in String address);
- void startAdvertising(in int appIf);
- void stopAdvertising();
- boolean setAdvServiceData(in byte[] serviceData);
- byte[] getAdvServiceData();
- boolean setAdvManufacturerCodeAndData(int manufactureCode, in byte[] manufacturerData);
- byte[] getAdvManufacturerData();
- List<ParcelUuid> getAdvServiceUuids();
- void removeAdvManufacturerCodeAndData(int manufacturerCode);
- boolean isAdvertising();
void refreshDevice(in int clientIf, in String address);
void discoverServices(in int clientIf, in String address);
void readCharacteristic(in int clientIf, in String address, in int srvcType,
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index af218eb..18e3f54 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -64,7 +64,6 @@
in int charInstId, in ParcelUuid charUuid,
in byte[] value);
void onReadRemoteRssi(in String address, in int rssi, in int status);
- void onAdvertiseStateChange(in int advertiseState, in int status);
void onMultiAdvertiseCallback(in int status);
void onConfigureMTU(in String address, in int mtu, in int status);
void onConnectionCongested(in String address, in boolean congested);
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index e232512..af79fcc 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -332,11 +332,6 @@
}
@Override
- public void onAdvertiseStateChange(int advertiseState, int status) {
- // no op
- }
-
- @Override
public void onMultiAdvertiseCallback(int status) {
// TODO: This logic needs to be re-visited to account
// for whether the scan has actually been started
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 7e87edc..220aa77 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -409,11 +409,6 @@
}
@Override
- public void onAdvertiseStateChange(int advertiseState, int status) {
- // no op
- }
-
- @Override
public void onMultiAdvertiseCallback(int status) {
// no op
}
diff --git a/core/java/android/content/AbstractRestrictionsProvider.java b/core/java/android/content/AbstractRestrictionsProvider.java
index 1119478..3272970 100644
--- a/core/java/android/content/AbstractRestrictionsProvider.java
+++ b/core/java/android/content/AbstractRestrictionsProvider.java
@@ -16,78 +16,65 @@
package android.content;
-import android.app.Service;
import android.app.admin.DevicePolicyManager;
import android.os.Bundle;
import android.os.IBinder;
/**
- * Abstract implementation of a Restrictions Provider Service. To implement a Restrictions Provider,
- * extend from this class and implement the abstract methods. Export this service in the
- * manifest. A profile owner device admin can then register this component as a Restrictions
- * Provider using {@link DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)}.
+ * Abstract implementation of a Restrictions Provider BroadcastReceiver. To implement a
+ * Restrictions Provider, extend from this class and implement the abstract methods.
+ * Export this receiver in the manifest. A profile owner device admin can then register this
+ * component as a Restrictions Provider using
+ * {@link DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)}.
* <p>
* The function of a Restrictions Provider is to transport permission requests from apps on this
* device to an administrator (most likely on a remote device or computer) and deliver back
* responses. The response should be sent back to the app via
* {@link RestrictionsManager#notifyPermissionResponse(String, Bundle)}.
- * <p>
- * Apps can also query previously received responses using
- * {@link #getPermissionResponse(String, String)}. The period for which previously received
- * responses are available is left to the implementation of the Restrictions Provider.
+ *
+ * @see RestrictionsManager
*/
-public abstract class AbstractRestrictionsProvider extends Service {
+public abstract class AbstractRestrictionsProvider extends BroadcastReceiver {
private static final String TAG = "AbstractRestrictionsProvider";
- @Override
- public final IBinder onBind(Intent intent) {
- return new RestrictionsProviderWrapper().asBinder();
- }
-
- /**
- * Checks to see if there is a response for a prior request and returns the response bundle if
- * it exists. If there is no response yet or if the request is not known, the returned bundle
- * should contain the response code in {@link RestrictionsManager#RESPONSE_KEY_RESULT}.
- *
- * @param packageName the application that is requesting a permission response.
- * @param requestId the id of the request for which the response is needed.
- * @return a bundle containing at a minimum the result of the request. It could contain other
- * optional information such as error codes and cookies.
- *
- * @see RestrictionsManager#RESPONSE_KEY_RESULT
- */
- public abstract Bundle getPermissionResponse(String packageName, String requestId);
-
/**
* An asynchronous permission request made by an application for an operation that requires
* authorization by a local or remote administrator other than the user. The Restrictions
- * Provider must transfer the request to the administrator and deliver back a response, when
+ * Provider should transfer the request to the administrator and deliver back a response, when
* available. The calling application is aware that the response could take an indefinite
* amount of time.
+ * <p>
+ * If the request bundle contains the key {@link RestrictionsManager#REQUEST_KEY_NEW_REQUEST},
+ * then a new request must be sent. Otherwise the provider can look up any previous response
+ * to the same requestId and return the cached response.
*
* @param packageName the application requesting permission.
* @param requestType the type of request, which determines the content and presentation of
* the request data.
* @param request the request data bundle containing at a minimum a request id.
*
- * @see RestrictionsManager#REQUEST_TYPE_QUESTION
+ * @see RestrictionsManager#REQUEST_TYPE_APPROVAL
* @see RestrictionsManager#REQUEST_TYPE_LOCAL_APPROVAL
* @see RestrictionsManager#REQUEST_KEY_ID
*/
- public abstract void requestPermission(String packageName, String requestType, Bundle request);
+ public abstract void requestPermission(Context context,
+ String packageName, String requestType, Bundle request);
- private class RestrictionsProviderWrapper extends IRestrictionsProvider.Stub {
+ /**
+ * Intercept standard Restrictions Provider broadcasts. Implementations
+ * should not override this method; it is better to implement the
+ * convenience callbacks for each action.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
- @Override
- public Bundle getPermissionResponse(String packageName, String requestId) {
- return AbstractRestrictionsProvider.this
- .getPermissionResponse(packageName, requestId);
- }
-
- @Override
- public void requestPermission(String packageName, String templateId, Bundle request) {
- AbstractRestrictionsProvider.this.requestPermission(packageName, templateId, request);
+ if (RestrictionsManager.ACTION_REQUEST_PERMISSION.equals(action)) {
+ String packageName = intent.getStringExtra(RestrictionsManager.EXTRA_PACKAGE_NAME);
+ String requestType = intent.getStringExtra(RestrictionsManager.EXTRA_REQUEST_TYPE);
+ Bundle request = intent.getBundleExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE);
+ requestPermission(context, packageName, requestType, request);
}
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 535aaa1..a52cbdd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -647,6 +647,26 @@
public abstract File getFilesDir();
/**
+ * Returns the absolute path to the directory on the filesystem similar to
+ * {@link #getFilesDir()}. The difference is that files placed under this
+ * directory will be excluded from automatic backup to remote storage. See
+ * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
+ * of the automatic backup mechanism in Android.
+ *
+ * <p>No permissions are required to read or write to the returned path, since this
+ * path is internal storage.
+ *
+ * @return The path of the directory holding application files that will not be
+ * automatically backed up to remote storage.
+ *
+ * @see #openFileOutput
+ * @see #getFileStreamPath
+ * @see #getDir
+ * @see android.app.backup.BackupAgent
+ */
+ public abstract File getNoBackupFilesDir();
+
+ /**
* Returns the absolute path to the directory on the primary external filesystem
* (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
* Environment.getExternalStorageDirectory()}) where the application can
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index dbf9122..13eed07 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -200,7 +200,12 @@
public File getFilesDir() {
return mBase.getFilesDir();
}
-
+
+ @Override
+ public File getNoBackupFilesDir() {
+ return mBase.getNoBackupFilesDir();
+ }
+
@Override
public File getExternalFilesDir(String type) {
return mBase.getExternalFilesDir(type);
diff --git a/core/java/android/content/IPermissionResponseCallback.aidl b/core/java/android/content/IPermissionResponseCallback.aidl
deleted file mode 100644
index 8309768..0000000
--- a/core/java/android/content/IPermissionResponseCallback.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
-** Copyright 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 android.content;
-
-import android.os.Bundle;
-
-/**
- * Callback for permission response queries.
- *
- * @hide
- */
- interface IPermissionResponseCallback {
-
- void onResponse(in Bundle response);
-
-}
diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl
index 49eb65b..b1c0a3a 100644
--- a/core/java/android/content/IRestrictionsManager.aidl
+++ b/core/java/android/content/IRestrictionsManager.aidl
@@ -17,7 +17,6 @@
package android.content;
import android.os.Bundle;
-import android.content.IPermissionResponseCallback;
/**
* Interface used by the RestrictionsManager
@@ -28,6 +27,4 @@
boolean hasRestrictionsProvider();
void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData);
void notifyPermissionResponse(in String packageName, in Bundle response);
- void getPermissionResponse(in String packageName, in String requestId,
- in IPermissionResponseCallback callback);
}
diff --git a/core/java/android/content/IRestrictionsProvider.aidl b/core/java/android/content/IRestrictionsProvider.aidl
deleted file mode 100644
index 4506b72..0000000
--- a/core/java/android/content/IRestrictionsProvider.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-** Copyright 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 android.content;
-
-import android.os.Bundle;
-
-/**
- * Interface to a restrictions provider service component.
- *
- * @hide
- */
- interface IRestrictionsProvider {
-
- void requestPermission(in String packageName, in String requestType, in Bundle requestBundle);
- Bundle getPermissionResponse(in String packageName, in String requestId);
-
-}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4153a02..5bf8a97 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3809,17 +3809,6 @@
* {@link android.R.styleable#AndroidManifestActivity_autoRemoveFromRecents}.
*/
public static final int FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS = 0x00002000;
- /**
- * If set along with FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
- * presented to the user but will instead be only available through the recents task list.
- * In addition, the new task wil be affiliated with the launching activity's task.
- * Affiliated tasks are grouped together in the recents task list.
- *
- * <p>This behavior is not supported for activities with {@link
- * android.R.styleable#AndroidManifestActivity_launchMode launchMode} values of
- * <code>singleInstance</code> or <code>singleTask</code>.
- */
- public static final int FLAG_ACTIVITY_LAUNCH_BEHIND = 0x00001000;
/**
* If set, when sending a broadcast only registered receivers will be
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 5ef2dbc..fa069a2 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -32,21 +32,17 @@
* <p>
* Apps can expose a set of restrictions via an XML file specified in the manifest.
* <p>
- * If the user has an active restrictions provider, dynamic requests can be made in
+ * If the user has an active Restrictions Provider, dynamic requests can be made in
* addition to the statically imposed restrictions. Dynamic requests are app-specific
* and can be expressed via a predefined set of request types.
* <p>
* The RestrictionsManager forwards the dynamic requests to the active
- * restrictions provider. The restrictions provider can respond back to requests by calling
+ * Restrictions Provider. The Restrictions Provider can respond back to requests by calling
* {@link #notifyPermissionResponse(String, Bundle)}, when
* a response is received from the administrator of the device or user.
* The response is relayed back to the application via a protected broadcast,
* {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}.
* <p>
- * Prior responses to requests can also be queried through calls to
- * {@link #getPermissionResponse(String, PermissionResponseCallback)}, if the provider
- * saves old responses.
- * <p>
* Static restrictions are specified by an XML file referenced by a meta-data attribute
* in the manifest. This enables applications as well as any web administration consoles
* to be able to read the list of available restrictions from the apk.
@@ -72,10 +68,8 @@
private static final String TAG = "RestrictionsManager";
/**
- * Broadcast intent delivered when a response is received for a permission
- * request. The response is not available for later query, so the receiver
- * must persist and/or immediately act upon the response. The application
- * should not interrupt the user by coming to the foreground if it isn't
+ * Broadcast intent delivered when a response is received for a permission request. The
+ * application should not interrupt the user by coming to the foreground if it isn't
* currently in the foreground. It can either post a notification informing
* the user of the response or wait until the next time the user launches the app.
* <p>
@@ -89,9 +83,32 @@
"android.intent.action.PERMISSION_RESPONSE_RECEIVED";
/**
+ * Broadcast intent sent to the Restrictions Provider to handle a permission request from
+ * an app. It will have the following extras: {@link #EXTRA_PACKAGE_NAME},
+ * {@link #EXTRA_REQUEST_TYPE} and {@link #EXTRA_REQUEST_BUNDLE}. The Restrictions Provider
+ * will handle the request and respond back to the RestrictionsManager, when a response is
+ * available, by calling {@link #notifyPermissionResponse}.
+ * <p>
+ * The BroadcastReceiver must require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN}
+ * permission to ensure that only the system can send the broadcast.
+ */
+ public static final String ACTION_REQUEST_PERMISSION =
+ "android.content.action.REQUEST_PERMISSION";
+
+ /**
* The package name of the application making the request.
*/
- public static final String EXTRA_PACKAGE_NAME = "package_name";
+ public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+
+ /**
+ * The request type passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+ */
+ public static final String EXTRA_REQUEST_TYPE = "android.content.extra.REQUEST_TYPE";
+
+ /**
+ * The request bundle passed in the {@link #ACTION_REQUEST_PERMISSION} broadcast.
+ */
+ public static final String EXTRA_REQUEST_BUNDLE = "android.content.extra.REQUEST_BUNDLE";
/**
* Contains a response from the administrator for specific request.
@@ -101,7 +118,7 @@
* <li>{@link #RESPONSE_KEY_RESULT}: The response result.</li>
* </ul>
*/
- public static final String EXTRA_RESPONSE_BUNDLE = "response";
+ public static final String EXTRA_RESPONSE_BUNDLE = "android.content.extra.RESPONSE_BUNDLE";
/**
* Request type for a simple question, with a possible title and icon.
@@ -113,7 +130,7 @@
* {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
* {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}.
*/
- public static final String REQUEST_TYPE_QUESTION = "android.request.type.question";
+ public static final String REQUEST_TYPE_APPROVAL = "android.request.type.approval";
/**
* Request type for a local password challenge. This is a way for an app to ask
@@ -204,22 +221,14 @@
public static final String REQUEST_KEY_DENY_LABEL = "android.request.deny_label";
/**
- * Key for requestor's name contained in the request bundle. This value is not specified by
- * the application. It is automatically inserted into the Bundle by the Restrictions Provider
- * before it is sent to the administrator.
+ * Key for issuing a new request, contained in the request bundle. If this is set to true,
+ * the Restrictions Provider must make a new request. If it is false or not specified, then
+ * the Restrictions Provider can return a cached response that has the same requestId, if
+ * available. If there's no cached response, it will issue a new one to the administrator.
* <p>
- * Type: String
+ * Type: boolean
*/
- public static final String REQUEST_KEY_REQUESTOR_NAME = "android.request.requestor";
-
- /**
- * Key for requestor's device name contained in the request bundle. This value is not specified
- * by the application. It is automatically inserted into the Bundle by the Restrictions Provider
- * before it is sent to the administrator.
- * <p>
- * Type: String
- */
- public static final String REQUEST_KEY_DEVICE_NAME = "android.request.device";
+ public static final String REQUEST_KEY_NEW_REQUEST = "android.request.new_request";
/**
* Key for the response in the response bundle sent to the application, for a permission
@@ -249,8 +258,8 @@
public static final int RESULT_NO_RESPONSE = 3;
/**
- * Response result value indicating that the request is unknown, when returned through a
- * call to #getPendingResponse
+ * Response result value indicating that the request is unknown, when it's not a new
+ * request.
*/
public static final int RESULT_UNKNOWN_REQUEST = 4;
@@ -312,19 +321,6 @@
private final IRestrictionsManager mService;
/**
- * Callback object for returning a response for a request.
- *
- * @see #getPermissionResponse
- */
- public static abstract class PermissionResponseCallback {
- /**
- * Contains the response
- * @param response
- */
- public abstract void onResponse(Bundle response);
- }
-
- /**
* @hide
*/
public RestrictionsManager(Context context, IRestrictionsManager service) {
@@ -350,11 +346,10 @@
}
/**
- * Called by an application to check if there is an active restrictions provider. If
- * there isn't, {@link #getPermissionResponse(String, PermissionResponseCallback)}
- * and {@link #requestPermission(String, Bundle)} are not available.
+ * Called by an application to check if there is an active Restrictions Provider. If
+ * there isn't, {@link #requestPermission(String, Bundle)} is not available.
*
- * @return whether there is an active restrictions provider.
+ * @return whether there is an active Restrictions Provider.
*/
public boolean hasRestrictionsProvider() {
try {
@@ -374,13 +369,24 @@
*
* @param requestType The type of request. The type could be one of the
* predefined types specified here or a custom type that the specific
- * restrictions provider might understand. For custom types, the type name should be
+ * Restrictions Provider might understand. For custom types, the type name should be
* namespaced to avoid collisions with predefined types and types specified by
- * other restrictions providers.
+ * other Restrictions Providers.
* @param request A Bundle containing the data corresponding to the specified request
* type. The keys for the data in the bundle depend on the request type.
+ *
+ * @throws IllegalArgumentException if any of the required parameters are missing.
*/
public void requestPermission(String requestType, Bundle request) {
+ if (requestType == null) {
+ throw new NullPointerException("requestType cannot be null");
+ }
+ if (request == null) {
+ throw new NullPointerException("request cannot be null");
+ }
+ if (!request.containsKey(REQUEST_KEY_ID)) {
+ throw new IllegalArgumentException("REQUEST_KEY_ID must be specified");
+ }
try {
if (mService != null) {
mService.requestPermission(mContext.getPackageName(), requestType, request);
@@ -391,41 +397,27 @@
}
/**
- * Called by an application to query for any available response from the restrictions provider
- * for the given requestId. The call returns immediately and the response will be returned
- * via the provided callback. This does not initiate a new request and does not wait
- * for a response to be received. It merely returns any previously received response
- * or indicates if there was no available response. If there are multiple responses
- * available for the same request ID, the most recent one is returned.
+ * Called by the Restrictions Provider to deliver a response to an application.
*
- * @param requestId The ID of the original request made via
- * {@link #requestPermission(String, Bundle)}. It's possible to also query for responses
- * to requests made on a different device with the same requestId, if the Restrictions
- * Provider happens to sync responses across devices with the same account managed by the
- * restrictions provider.
- * @param callback The response is returned via the callback object. Cannot be null.
- */
- public void getPermissionResponse(String requestId, PermissionResponseCallback callback) {
- if (requestId == null || callback == null) {
- throw new NullPointerException("requestId or callback cannot be null");
- }
- try {
- if (mService != null) {
- mService.getPermissionResponse(mContext.getPackageName(), requestId,
- new PermissionResponseCallbackWrapper(callback));
- }
- } catch (RemoteException re) {
- Log.w(TAG, "Couldn't reach service");
- }
- }
-
- /**
- * Called by the restrictions provider to deliver a response to an application.
- *
- * @param packageName the application to deliver the response to.
+ * @param packageName the application to deliver the response to. Cannot be null.
* @param response the Bundle containing the response status, request ID and other information.
+ * Cannot be null.
+ *
+ * @throws IllegalArgumentException if any of the required parameters are missing.
*/
public void notifyPermissionResponse(String packageName, Bundle response) {
+ if (packageName == null) {
+ throw new NullPointerException("packageName cannot be null");
+ }
+ if (response == null) {
+ throw new NullPointerException("request cannot be null");
+ }
+ if (!response.containsKey(REQUEST_KEY_ID)) {
+ throw new IllegalArgumentException("REQUEST_KEY_ID must be specified");
+ }
+ if (!response.containsKey(RESPONSE_KEY_RESULT)) {
+ throw new IllegalArgumentException("RESPONSE_KEY_RESULT must be specified");
+ }
try {
if (mService != null) {
mService.notifyPermissionResponse(packageName, response);
@@ -447,19 +439,4 @@
// TODO:
return null;
}
-
- private static class PermissionResponseCallbackWrapper
- extends IPermissionResponseCallback.Stub {
-
- private PermissionResponseCallback mCallback;
-
- PermissionResponseCallbackWrapper(PermissionResponseCallback callback) {
- mCallback = callback;
- }
-
- @Override
- public void onResponse(Bundle response) {
- mCallback.onResponse(response);
- }
- }
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 58d3526..3a98f5d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -429,4 +429,9 @@
boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId);
boolean getBlockUninstallForUser(String packageName, int userId);
+
+ IBinder getKeySetByAlias(String packageName, String alias);
+ IBinder getSigningKeySet(String packageName);
+ boolean isPackageSignedByKeySet(String packageName, IBinder ks);
+ boolean isPackageSignedByKeySetExactly(String packageName, IBinder ks);
}
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
index 45606d1..3336727 100644
--- a/core/java/android/content/pm/InstallSessionInfo.java
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -20,15 +20,25 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** {@hide} */
+/**
+ * Details for an active install session.
+ */
public class InstallSessionInfo implements Parcelable {
+
+ /** {@hide} */
public int sessionId;
+ /** {@hide} */
public String installerPackageName;
+ /** {@hide} */
public int progress;
- public boolean fullInstall;
+ /** {@hide} */
+ public int mode;
+ /** {@hide} */
public String packageName;
+ /** {@hide} */
public Bitmap icon;
+ /** {@hide} */
public CharSequence title;
/** {@hide} */
@@ -41,12 +51,62 @@
installerPackageName = source.readString();
progress = source.readInt();
- fullInstall = source.readInt() != 0;
+ mode = source.readInt();
packageName = source.readString();
icon = source.readParcelable(null);
title = source.readString();
}
+ /**
+ * Return the ID for this session.
+ */
+ public int getSessionId() {
+ return sessionId;
+ }
+
+ /**
+ * Return the package name of the app that owns this session.
+ */
+ public String getInstallerPackageName() {
+ return installerPackageName;
+ }
+
+ /**
+ * Return current overall progress of this session, between 0 and 100.
+ * <p>
+ * Note that this progress may not directly correspond to the value reported
+ * by {@link PackageInstaller.Session#setProgress(int)}, as the system may
+ * carve out a portion of the overall progress to represent its own internal
+ * installation work.
+ */
+ public int getProgress() {
+ return progress;
+ }
+
+ /**
+ * Return the package name this session is working with. May be {@code null}
+ * if unknown.
+ */
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /**
+ * Return an icon representing the app being installed. May be {@code null}
+ * if unavailable.
+ */
+ public Bitmap getIcon() {
+ return icon;
+ }
+
+ /**
+ * Return a title representing the app being installed. May be {@code null}
+ * if unavailable.
+ */
+ public CharSequence getTitle() {
+ return title;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -58,7 +118,7 @@
dest.writeString(installerPackageName);
dest.writeInt(progress);
- dest.writeInt(fullInstall ? 1 : 0);
+ dest.writeInt(mode);
dest.writeString(packageName);
dest.writeParcelable(icon, flags);
dest.writeString(title != null ? title.toString() : null);
diff --git a/core/java/android/content/pm/InstallSessionParams.java b/core/java/android/content/pm/InstallSessionParams.java
index 43e3314..e039699 100644
--- a/core/java/android/content/pm/InstallSessionParams.java
+++ b/core/java/android/content/pm/InstallSessionParams.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Parcel;
@@ -23,13 +24,22 @@
import com.android.internal.util.IndentingPrintWriter;
-/** {@hide} */
+/**
+ * Parameters for creating a new {@link PackageInstaller.Session}.
+ */
public class InstallSessionParams implements Parcelable {
// TODO: extend to support remaining VerificationParams
/** {@hide} */
- public boolean fullInstall;
+ public static final int MODE_INVALID = 0;
+ /** {@hide} */
+ public static final int MODE_FULL_INSTALL = 1;
+ /** {@hide} */
+ public static final int MODE_INHERIT_EXISTING = 2;
+
+ /** {@hide} */
+ public int mode = MODE_INVALID;
/** {@hide} */
public int installFlags;
/** {@hide} */
@@ -58,7 +68,7 @@
/** {@hide} */
public InstallSessionParams(Parcel source) {
- fullInstall = source.readInt() != 0;
+ mode = source.readInt();
installFlags = source.readInt();
installLocation = source.readInt();
signatures = (Signature[]) source.readParcelableArray(null);
@@ -72,53 +82,108 @@
abiOverride = source.readString();
}
- public void setFullInstall(boolean fullInstall) {
- this.fullInstall = fullInstall;
+ /**
+ * Set session mode indicating that it should fully replace any existing
+ * APKs for this application.
+ */
+ public void setModeFullInstall() {
+ this.mode = MODE_FULL_INSTALL;
}
- public void setInstallFlags(int installFlags) {
- this.installFlags = installFlags;
+ /**
+ * Set session mode indicating that it should inherit any existing APKs for
+ * this application, unless they are explicitly overridden (by split name)
+ * in the session.
+ */
+ public void setModeInheritExisting() {
+ this.mode = MODE_INHERIT_EXISTING;
}
+ /**
+ * Provide value of {@link PackageInfo#installLocation}, which may be used
+ * to determine where the app will be staged. Defaults to
+ * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}.
+ */
public void setInstallLocation(int installLocation) {
this.installLocation = installLocation;
}
+ /**
+ * Optionally provide the required value of {@link PackageInfo#signatures}.
+ * This can be used to assert that all staged APKs have been signed with
+ * this set of specific certificates. Regardless of this value, all APKs in
+ * the application must have the same signing certificates.
+ */
public void setSignatures(Signature[] signatures) {
this.signatures = signatures;
}
+ /**
+ * Indicate the expected growth in disk usage resulting from this session.
+ * This may be used to ensure enough disk space exists before proceeding, or
+ * to estimate container size for installations living on external storage.
+ * <p>
+ * This value should only reflect APK sizes.
+ */
public void setDeltaSize(long deltaSize) {
this.deltaSize = deltaSize;
}
+ /**
+ * Set the maximum progress for this session, used for normalization
+ * purposes.
+ *
+ * @see PackageInstaller.Session#setProgress(int)
+ */
public void setProgressMax(int progressMax) {
this.progressMax = progressMax;
}
+ /**
+ * Optionally set the package name this session will be working with. It's
+ * strongly recommended that you provide this value when known.
+ */
public void setPackageName(String packageName) {
this.packageName = packageName;
}
+ /**
+ * Optionally set an icon representing the app being installed.
+ */
public void setIcon(Bitmap icon) {
this.icon = icon;
}
+ /**
+ * Optionally set a title representing the app being installed.
+ */
public void setTitle(CharSequence title) {
this.title = title;
}
+ /**
+ * Optionally set the URI where this package was downloaded from. Used for
+ * verification purposes.
+ *
+ * @see Intent#EXTRA_ORIGINATING_URI
+ */
public void setOriginatingUri(Uri originatingUri) {
this.originatingUri = originatingUri;
}
+ /**
+ * Optionally set the URI that referred you to install this package. Used
+ * for verification purposes.
+ *
+ * @see Intent#EXTRA_REFERRER
+ */
public void setReferrerUri(Uri referrerUri) {
this.referrerUri = referrerUri;
}
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
- pw.printPair("fullInstall", fullInstall);
+ pw.printPair("mode", mode);
pw.printHexPair("installFlags", installFlags);
pw.printPair("installLocation", installLocation);
pw.printPair("signatures", (signatures != null));
@@ -140,7 +205,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(fullInstall ? 1 : 0);
+ dest.writeInt(mode);
dest.writeInt(installFlags);
dest.writeInt(installLocation);
dest.writeParcelableArray(signatures, flags);
diff --git a/core/java/android/content/pm/KeySet.java b/core/java/android/content/pm/KeySet.java
index 0ef09a4..fcdaa18 100644
--- a/core/java/android/content/pm/KeySet.java
+++ b/core/java/android/content/pm/KeySet.java
@@ -16,19 +16,36 @@
package android.content.pm;
-import android.os.Binder;
+import android.os.IBinder;
-/** @hide */
+/**
+ * Represents a {@code KeySet} that has been declared in the AndroidManifest.xml
+ * file for the application. A {@code KeySet} can be used explicitly to
+ * represent a trust relationship with other applications on the device.
+ */
public class KeySet {
- private Binder token;
+ private IBinder token;
/** @hide */
- public KeySet(Binder token) {
+ public KeySet(IBinder token) {
+ if (token == null) {
+ throw new NullPointerException("null value for KeySet IBinder token");
+ }
this.token = token;
}
- Binder getToken() {
+ /** @hide */
+ public IBinder getToken() {
return token;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof KeySet) {
+ KeySet ks = (KeySet) o;
+ return token == ks.token;
+ }
+ return false;
+ }
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index ef0c4d5..8f0c249 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -31,6 +31,11 @@
public String packageName;
/**
+ * The names of any installed split APKs for this package.
+ */
+ public String[] splitNames;
+
+ /**
* The version number of this package, as specified by the <manifest>
* tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode}
* attribute.
@@ -190,24 +195,25 @@
* @hide
*/
public static final int INSTALL_LOCATION_UNSPECIFIED = -1;
+
/**
- * Constant corresponding to <code>auto</code> in
- * the {@link android.R.attr#installLocation} attribute.
- * @hide
+ * Constant corresponding to <code>auto</code> in the
+ * {@link android.R.attr#installLocation} attribute.
*/
public static final int INSTALL_LOCATION_AUTO = 0;
+
/**
- * Constant corresponding to <code>internalOnly</code> in
- * the {@link android.R.attr#installLocation} attribute.
- * @hide
+ * Constant corresponding to <code>internalOnly</code> in the
+ * {@link android.R.attr#installLocation} attribute.
*/
public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1;
+
/**
- * Constant corresponding to <code>preferExternal</code> in
- * the {@link android.R.attr#installLocation} attribute.
- * @hide
+ * Constant corresponding to <code>preferExternal</code> in the
+ * {@link android.R.attr#installLocation} attribute.
*/
public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;
+
/**
* Flag for {@link #requiredForProfile}
* The application will always be installed for a restricted profile.
@@ -222,12 +228,10 @@
public static final int MANAGED_PROFILE = 2;
/**
- * The install location requested by the activity. From the
+ * The install location requested by the package. From the
* {@link android.R.attr#installLocation} attribute, one of
- * {@link #INSTALL_LOCATION_AUTO},
- * {@link #INSTALL_LOCATION_INTERNAL_ONLY},
+ * {@link #INSTALL_LOCATION_AUTO}, {@link #INSTALL_LOCATION_INTERNAL_ONLY},
* {@link #INSTALL_LOCATION_PREFER_EXTERNAL}
- * @hide
*/
public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
@@ -269,6 +273,7 @@
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeString(packageName);
+ dest.writeStringArray(splitNames);
dest.writeInt(versionCode);
dest.writeString(versionName);
dest.writeString(sharedUserId);
@@ -314,6 +319,7 @@
private PackageInfo(Parcel source) {
packageName = source.readString();
+ splitNames = source.readStringArray();
versionCode = source.readInt();
versionName = source.readString();
sharedUserId = source.readString();
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 50a0483..e336c5f 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -73,6 +73,7 @@
dest.writeInt(versionCode);
dest.writeInt(recommendedInstallLocation);
dest.writeInt(installLocation);
+ dest.writeInt(multiArch ? 1 : 0);
if (verifiers == null || verifiers.length == 0) {
dest.writeInt(0);
@@ -98,6 +99,7 @@
versionCode = source.readInt();
recommendedInstallLocation = source.readInt();
installLocation = source.readInt();
+ multiArch = (source.readInt() != 0);
final int verifiersLength = source.readInt();
if (verifiersLength == 0) {
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 401be06..348a7ad 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -30,7 +30,31 @@
import java.io.OutputStream;
import java.util.List;
-/** {@hide} */
+/**
+ * Offers the ability to install, upgrade, and remove applications on the
+ * device. This includes support for apps packaged either as a single
+ * "monolithic" APK, or apps packaged as multiple "split" APKs.
+ * <p>
+ * An app is delivered for installation through a
+ * {@link PackageInstaller.Session}, which any app can create. Once the session
+ * is created, the installer can stream one or more APKs into place until it
+ * decides to either commit or destroy the session. Committing may require user
+ * intervention to complete the installation.
+ * <p>
+ * Sessions can install brand new apps, upgrade existing apps, or add new splits
+ * onto an existing app.
+ * <p>
+ * Apps packaged into multiple split APKs always consist of a single "base" APK
+ * (with a {@code null} split name) and zero or more "split" APKs (with unique
+ * split names). Any subset of these APKs can be installed together, as long as
+ * the following constraints are met:
+ * <ul>
+ * <li>All APKs must have the exact same package name, version code, and signing
+ * certificates.
+ * <li>All installations must contain a single base APK.
+ * <li>All APKs must have unique split names.
+ * </ul>
+ */
public class PackageInstaller {
private final PackageManager mPm;
private final IPackageInstaller mInstaller;
@@ -46,16 +70,18 @@
mUserId = userId;
}
+ /**
+ * Quickly test if the given package is already available on the device.
+ * This is typically used in multi-user scenarios where another user on the
+ * device has already installed the package.
+ *
+ * @hide
+ */
public boolean isPackageAvailable(String packageName) {
- try {
- final ApplicationInfo info = mPm.getApplicationInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES);
- return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
- } catch (NameNotFoundException e) {
- return false;
- }
+ return mPm.isPackageAvailable(packageName);
}
+ /** {@hide} */
public void installAvailablePackage(String packageName, PackageInstallObserver observer) {
int returnCode;
try {
@@ -66,6 +92,18 @@
observer.packageInstalled(packageName, null, returnCode);
}
+ /**
+ * Create a new session using the given parameters, returning a unique ID
+ * that represents the session. Once created, the session can be opened
+ * multiple times across multiple device boots.
+ * <p>
+ * The system may automatically destroy sessions that have not been
+ * finalized (either committed or abandoned) within a reasonable period of
+ * time, typically on the order of a day.
+ *
+ * @throws IOException if parameters were unsatisfiable, such as lack of
+ * disk space or unavailable media.
+ */
public int createSession(InstallSessionParams params) throws IOException {
try {
return mInstaller.createSession(mInstallerPackageName, params, mUserId);
@@ -77,6 +115,9 @@
}
}
+ /**
+ * Open an existing session to actively perform work.
+ */
public Session openSession(int sessionId) {
try {
return new Session(mInstaller.openSession(sessionId));
@@ -85,7 +126,10 @@
}
}
- public List<InstallSessionInfo> getSessions() {
+ /**
+ * Return list of all active install sessions on the device.
+ */
+ public List<InstallSessionInfo> getActiveSessions() {
// TODO: filter based on caller
// TODO: let launcher app see all active sessions
try {
@@ -95,6 +139,11 @@
}
}
+ /**
+ * Uninstall the given package, removing it completely from the device. This
+ * method is only available to the current "installer of record" for the
+ * package.
+ */
public void uninstall(String packageName, UninstallResultCallback callback) {
try {
mInstaller.uninstall(packageName, 0,
@@ -104,6 +153,11 @@
}
}
+ /**
+ * Uninstall only a specific split from the given package.
+ *
+ * @hide
+ */
public void uninstall(String packageName, String splitName, UninstallResultCallback callback) {
try {
mInstaller.uninstallSplit(packageName, splitName, 0,
@@ -113,6 +167,9 @@
}
}
+ /**
+ * Events for observing session lifecycle.
+ */
public static abstract class SessionObserver {
private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() {
@Override
@@ -127,7 +184,7 @@
@Override
public void onSessionFinished(int sessionId, boolean success) {
- SessionObserver.this.onFinished(sessionId, success);
+ SessionObserver.this.onFinalized(sessionId, success);
}
};
@@ -136,11 +193,30 @@
return mBinder;
}
+ /**
+ * New session has been created.
+ */
public abstract void onCreated(InstallSessionInfo info);
+
+ /**
+ * Progress for given session has been updated.
+ * <p>
+ * Note that this progress may not directly correspond to the value
+ * reported by {@link PackageInstaller.Session#setProgress(int)}, as the
+ * system may carve out a portion of the overall progress to represent
+ * its own internal installation work.
+ */
public abstract void onProgress(int sessionId, int progress);
- public abstract void onFinished(int sessionId, boolean success);
+
+ /**
+ * Session has been finalized, either with success or failure.
+ */
+ public abstract void onFinalized(int sessionId, boolean success);
}
+ /**
+ * Register to watch for session lifecycle events.
+ */
public void registerSessionObserver(SessionObserver observer) {
try {
mInstaller.registerObserver(observer.getBinder(), mUserId);
@@ -149,6 +225,9 @@
}
}
+ /**
+ * Unregister an existing observer.
+ */
public void unregisterSessionObserver(SessionObserver observer) {
try {
mInstaller.unregisterObserver(observer.getBinder(), mUserId);
@@ -177,6 +256,10 @@
mSession = session;
}
+ /**
+ * Set current progress. Valid values are anywhere between 0 and
+ * {@link InstallSessionParams#setProgressMax(int)}.
+ */
public void setProgress(int progress) {
try {
mSession.setClientProgress(progress);
@@ -197,7 +280,7 @@
/**
* Open an APK file for writing, starting at the given offset. You can
* then stream data into the file, periodically calling
- * {@link OutputStream#flush()} to ensure bytes have been written to
+ * {@link #fsync(OutputStream)} to ensure bytes have been written to
* disk.
*/
public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes)
@@ -214,6 +297,11 @@
}
}
+ /**
+ * Ensure that any outstanding data for given stream has been committed
+ * to disk. This is only valid for streams returned from
+ * {@link #openWrite(String, long, long)}.
+ */
public void fsync(OutputStream out) throws IOException {
if (out instanceof FileBridge.FileBridgeOutputStream) {
((FileBridge.FileBridgeOutputStream) out).fsync();
@@ -222,6 +310,15 @@
}
}
+ /**
+ * Attempt to commit everything staged in this session. This may require
+ * user intervention, and so it may not happen immediately. The final
+ * result of the commit will be reported through the given callback.
+ * <p>
+ * Once this method is called, no additional mutations may be performed
+ * on the session. If the device reboots before the session has been
+ * finalized, you may commit the session again.
+ */
public void commit(CommitResultCallback callback) {
try {
mSession.install(new CommitResultCallbackDelegate(callback).getBinder());
@@ -230,11 +327,18 @@
}
}
+ /**
+ * Release this session object. You can open the session again if it
+ * hasn't been finalized.
+ */
@Override
public void close() {
// No resources to release at the moment
}
+ /**
+ * Completely destroy this session, rendering it invalid.
+ */
public void destroy() {
try {
mSession.destroy();
@@ -244,11 +348,15 @@
}
}
+ /**
+ * Final result of an uninstall request.
+ */
public static abstract class UninstallResultCallback {
public abstract void onSuccess();
public abstract void onFailure(String msg);
}
+ /** {@hide} */
private static class UninstallResultCallbackDelegate extends PackageUninstallObserver {
private final UninstallResultCallback target;
@@ -271,8 +379,18 @@
}
}
+ /**
+ * Final result of a session commit request.
+ */
public static abstract class CommitResultCallback {
public abstract void onSuccess();
+
+ /**
+ * Generic failure occurred. You can override methods (such as
+ * {@link #onFailureInvalid(String)}) to handle more specific categories
+ * of failure. By default, those specific categories all flow into this
+ * generic failure.
+ */
public abstract void onFailure(String msg);
/**
@@ -286,12 +404,16 @@
}
/**
- * This install session conflicts (or is inconsistent with) with
- * another package already installed on the device. For example, an
- * existing permission, incompatible certificates, etc. The user may
- * be able to uninstall another app to fix the issue.
+ * This install session conflicts (or is inconsistent with) with another
+ * package already installed on the device. For example, an existing
+ * permission, incompatible certificates, etc. The user may be able to
+ * uninstall another app to fix the issue.
+ *
+ * @param otherPackageName if one specific package was identified as the
+ * cause of the conflict, it's named here. If unknown, or
+ * multiple packages, this may be {@code null}.
*/
- public void onFailureConflict(String msg, String packageName) {
+ public void onFailureConflict(String msg, String otherPackageName) {
onFailure(msg);
}
@@ -317,6 +439,7 @@
}
}
+ /** {@hide} */
private static class CommitResultCallbackDelegate extends PackageInstallObserver {
private final CommitResultCallback target;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 03d4701..052c2ca 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1167,6 +1167,22 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a relative humidity sensor.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_RELATIVE_HUMIDITY =
+ "android.hardware.sensor.relative_humidity";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes an ambient temperature sensor.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE =
+ "android.hardware.sensor.ambient_temperature";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a telephony radio with data
* communication support.
*/
@@ -3602,6 +3618,33 @@
public abstract boolean isSafeMode();
/**
+ * Return the {@link KeySet} associated with the String alias for this
+ * application.
+ *
+ * @param alias The alias for a given {@link KeySet} as defined in the
+ * application's AndroidManifest.xml.
+ */
+ public abstract KeySet getKeySetByAlias(String packageName, String alias);
+
+ /** Return the signing {@link KeySet} for this application. */
+ public abstract KeySet getSigningKeySet(String packageName);
+
+ /**
+ * Return whether the package denoted by packageName has been signed by all
+ * of the keys specified by the {@link KeySet} ks. This will return true if
+ * the package has been signed by additional keys (a superset) as well.
+ * Compare to {@link #isSignedByExactly(String packageName, KeySet ks)}.
+ */
+ public abstract boolean isSignedBy(String packageName, KeySet ks);
+
+ /**
+ * Return whether the package denoted by packageName has been signed by all
+ * of, and only, the keys specified by the {@link KeySet} ks. Compare to
+ * {@link #isSignedBy(String packageName, KeySet ks)}.
+ */
+ public abstract boolean isSignedByExactly(String packageName, KeySet ks);
+
+ /**
* Attempts to move package resources from internal to external media or vice versa.
* Since this may take a little while, the result will
* be posted back to the given observer. This call may fail if the calling context
@@ -3629,8 +3672,11 @@
*/
public abstract VerifierDeviceIdentity getVerifierDeviceIdentity();
- /** {@hide} */
- public abstract PackageInstaller getPackageInstaller();
+ /**
+ * Return interface that offers the ability to install, upgrade, and remove
+ * applications on the device.
+ */
+ public abstract PackageInstaller getInstaller();
/**
* Returns the data directory for a particular user and package, given the uid of the package.
@@ -3676,4 +3722,7 @@
* @hide
*/
public abstract Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo);
+
+ /** {@hide} */
+ public abstract boolean isPackageAvailable(String packageName);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d92a90d..7bfe55d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -380,6 +380,7 @@
}
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
+ pi.splitNames = p.splitNames;
pi.versionCode = p.mVersionCode;
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
@@ -1128,11 +1129,8 @@
} else if (attr.equals("versionCode")) {
versionCode = attrs.getAttributeIntValue(i, 0);
numFound++;
- } else if (attr.equals("multiArch")) {
- multiArch = attrs.getAttributeBooleanValue(i, false);
- numFound++;
}
- if (numFound >= 3) {
+ if (numFound >= 2) {
break;
}
}
@@ -1154,6 +1152,16 @@
verifiers.add(verifier);
}
}
+
+ if (parser.getDepth() == searchDepth && "application".equals(parser.getName())) {
+ for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+ final String attr = attrs.getAttributeName(i);
+ if ("multiArch".equals(attr)) {
+ multiArch = attrs.getAttributeBooleanValue(i, false);
+ break;
+ }
+ }
+ }
}
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 9625578..38ddede 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1711,7 +1711,7 @@
String locale = null;
if (mConfiguration.locale != null) {
- locale = adjustLanguageTag(localeToLanguageTag(mConfiguration.locale));
+ locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag());
}
int width, height;
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
@@ -1800,12 +1800,6 @@
}
}
- // Locale.toLanguageTag() is not available in Java6. LayoutLib overrides
- // this method to enable users to use Java6.
- private String localeToLanguageTag(Locale locale) {
- return locale.toLanguageTag();
- }
-
/**
* {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
* language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index e9793c4..5f29e5c 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -17,6 +17,7 @@
package android.hardware.camera2.legacy;
import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
@@ -463,6 +464,24 @@
return ids.contains(id);
}
+ static void setSurfaceOrientation(Surface surface, int facing, int sensorOrientation)
+ throws BufferQueueAbandonedException {
+ checkNotNull(surface);
+ LegacyExceptionUtils.throwOnError(nativeSetSurfaceOrientation(surface, facing,
+ sensorOrientation));
+ }
+
+ static Size getTextureSize(SurfaceTexture surfaceTexture)
+ throws BufferQueueAbandonedException {
+ checkNotNull(surfaceTexture);
+
+ int[] dimens = new int[2];
+ LegacyExceptionUtils.throwOnError(nativeDetectTextureDimens(surfaceTexture,
+ /*out*/dimens));
+
+ return new Size(dimens[0], dimens[1]);
+ }
+
private static native int nativeDetectSurfaceType(Surface surface);
private static native int nativeDetectSurfaceDimens(Surface surface,
@@ -479,4 +498,11 @@
private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);
private static native long nativeGetSurfaceId(Surface surface);
+
+ private static native int nativeSetSurfaceOrientation(Surface surface, int facing,
+ int sensorOrientation);
+
+ private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture,
+ /*out*/int[/*2*/] dimens);
+
}
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index e6d84c5..7d1be3b 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -292,10 +292,13 @@
mInFlightPreview = null;
mInFlightJpeg = null;
+ int facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
+ int orientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
if (outputs != null) {
for (Surface s : outputs) {
try {
int format = LegacyCameraDevice.detectSurfaceType(s);
+ LegacyCameraDevice.setSurfaceOrientation(s, facing, orientation);
switch (format) {
case CameraMetadataNative.NATIVE_JPEG_FORMAT:
mCallbackOutputs.add(s);
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index daa64c0..fdf9ba0 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -1,18 +1,18 @@
/*
-* 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.
-*/
+ * 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 android.hardware.camera2.legacy;
import android.graphics.ImageFormat;
@@ -98,14 +98,14 @@
*/
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
- "uniform mat4 uSTMatrix;\n" +
- "attribute vec4 aPosition;\n" +
- "attribute vec4 aTextureCoord;\n" +
- "varying vec2 vTextureCoord;\n" +
- "void main() {\n" +
- " gl_Position = uMVPMatrix * aPosition;\n" +
- " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
- "}\n";
+ "uniform mat4 uSTMatrix;\n" +
+ "attribute vec4 aPosition;\n" +
+ "attribute vec4 aTextureCoord;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = uMVPMatrix * aPosition;\n" +
+ " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+ "}\n";
/**
* This fragment shader simply draws the color in the 2D texture at
@@ -113,12 +113,12 @@
*/
private static final String FRAGMENT_SHADER =
"#extension GL_OES_EGL_image_external : require\n" +
- "precision mediump float;\n" +
- "varying vec2 vTextureCoord;\n" +
- "uniform samplerExternalOES sTexture;\n" +
- "void main() {\n" +
- " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
- "}\n";
+ "precision mediump float;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "uniform samplerExternalOES sTexture;\n" +
+ "void main() {\n" +
+ " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+ "}\n";
private float[] mMVPMatrix = new float[GL_MATRIX_SIZE];
private float[] mSTMatrix = new float[GL_MATRIX_SIZE];
@@ -189,12 +189,56 @@
return program;
}
- private void drawFrame(SurfaceTexture st) {
+ private void drawFrame(SurfaceTexture st, int width, int height) {
checkGlError("onDrawFrame start");
st.getTransformMatrix(mSTMatrix);
+ Size dimens;
+ try {
+ dimens = LegacyCameraDevice.getTextureSize(st);
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ // Should never hit this.
+ throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e);
+ }
+
+ Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0);
+
+ float texWidth = dimens.getWidth();
+ float texHeight = dimens.getHeight();
+
+ if (texWidth <= 0 || texHeight <= 0) {
+ throw new IllegalStateException("Illegal intermediate texture with dimension of 0");
+ }
+
+ // Find largest scaling factor from the intermediate texture dimension to the
+ // output surface dimension. Scaling the intermediate texture by this allows
+ // us to letterbox/pillerbox the output surface into the intermediate texture.
+ float widthRatio = width / texWidth;
+ float heightRatio = height / texHeight;
+ float actual = (widthRatio < heightRatio) ? heightRatio : widthRatio;
+
if (DEBUG) {
- GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ Log.d(TAG, "Scaling factor " + actual + " used for " + width + "x" + height +
+ " surface, intermediate buffer size is " + texWidth + "x" + texHeight);
+ }
+
+ // Set the viewport height and width to be the scaled intermediate texture dimensions.
+ int viewportW = (int) (actual * texWidth);
+ int viewportH = (int) (actual * texHeight);
+
+ // Set the offset of the viewport so that the output surface is centered in the viewport.
+ float dx = (width - viewportW) / 2f;
+ float dy = (height - viewportH) / 2f;
+
+ if (DEBUG) {
+ Log.d(TAG, "Translation " + dx + "," + dy + " used for " + width + "x" + height +
+ " surface");
+ }
+
+ GLES20.glViewport((int) dx, (int) dy, viewportW, viewportH);
+
+ if (DEBUG) {
+ GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
}
@@ -218,7 +262,6 @@
GLES20.glEnableVertexAttribArray(maTextureHandle);
checkGlError("glEnableVertexAttribArray maTextureHandle");
- Matrix.setIdentityM(mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix,
/*offset*/ 0);
GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix,
@@ -589,18 +632,19 @@
for (EGLSurfaceHolder holder : mSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
makeCurrent(holder.eglSurface);
- drawFrame(mSurfaceTexture);
+ drawFrame(mSurfaceTexture, holder.width, holder.height);
swapBuffers(holder.eglSurface);
}
}
for (EGLSurfaceHolder holder : mConversionSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
makeCurrent(holder.eglSurface);
- drawFrame(mSurfaceTexture);
+ drawFrame(mSurfaceTexture, holder.width, holder.height);
mPBufferPixels.clear();
- GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, GLES20.GL_RGBA,
- GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
+ GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height,
+ GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
checkGlError("glReadPixels");
+
try {
int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index c22c4b6..aab7b1f 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -120,6 +120,16 @@
public static final int PROCESS_STATE = 12;
/**
+ * A constant indicating a sync timer
+ */
+ public static final int SYNC = 13;
+
+ /**
+ * A constant indicating a job timer
+ */
+ public static final int JOB = 14;
+
+ /**
* Include all of the data in the stats, including previously saved data.
*/
public static final int STATS_SINCE_CHARGED = 0;
@@ -142,7 +152,7 @@
/**
* Bump the version on this if the checkin format changes.
*/
- private static final int BATTERY_STATS_CHECKIN_VERSION = 8;
+ private static final int BATTERY_STATS_CHECKIN_VERSION = 9;
private static final long BYTES_PER_KB = 1024;
private static final long BYTES_PER_MB = 1048576; // 1024^2
@@ -157,6 +167,8 @@
private static final String FOREGROUND_DATA = "fg";
private static final String STATE_TIME_DATA = "st";
private static final String WAKELOCK_DATA = "wl";
+ private static final String SYNC_DATA = "sy";
+ private static final String JOB_DATA = "jb";
private static final String KERNEL_WAKELOCK_DATA = "kwl";
private static final String WAKEUP_REASON_DATA = "wr";
private static final String NETWORK_DATA = "nt";
@@ -273,6 +285,20 @@
public abstract Map<String, ? extends Wakelock> getWakelockStats();
/**
+ * Returns a mapping containing sync statistics.
+ *
+ * @return a Map from Strings to Timer objects.
+ */
+ public abstract Map<String, ? extends Timer> getSyncStats();
+
+ /**
+ * Returns a mapping containing scheduled job statistics.
+ *
+ * @return a Map from Strings to Timer objects.
+ */
+ public abstract Map<String, ? extends Timer> getJobStats();
+
+ /**
* The statistics associated with a particular wake lock.
*/
public static abstract class Wakelock {
@@ -660,13 +686,19 @@
public static final int EVENT_FOREGROUND = 0x0002;
// Event is about an application package that is at the top of the screen.
public static final int EVENT_TOP = 0x0003;
- // Event is about an application package that is at the top of the screen.
+ // Event is about active sync operations.
public static final int EVENT_SYNC = 0x0004;
// Events for all additional wake locks aquired/release within a wake block.
// These are not generated by default.
public static final int EVENT_WAKE_LOCK = 0x0005;
+ // Event is about an application executing a scheduled job.
+ public static final int EVENT_JOB = 0x0006;
+ // Events for users running.
+ public static final int EVENT_USER_RUNNING = 0x0007;
+ // Events for foreground user.
+ public static final int EVENT_USER_FOREGROUND = 0x0008;
// Number of event types.
- public static final int EVENT_COUNT = 0x0006;
+ public static final int EVENT_COUNT = 0x0009;
// Mask to extract out only the type part of the event.
public static final int EVENT_TYPE_MASK = ~(EVENT_FLAG_START|EVENT_FLAG_FINISH);
@@ -680,6 +712,14 @@
public static final int EVENT_SYNC_FINISH = EVENT_SYNC | EVENT_FLAG_FINISH;
public static final int EVENT_WAKE_LOCK_START = EVENT_WAKE_LOCK | EVENT_FLAG_START;
public static final int EVENT_WAKE_LOCK_FINISH = EVENT_WAKE_LOCK | EVENT_FLAG_FINISH;
+ public static final int EVENT_JOB_START = EVENT_JOB | EVENT_FLAG_START;
+ public static final int EVENT_JOB_FINISH = EVENT_JOB | EVENT_FLAG_FINISH;
+ public static final int EVENT_USER_RUNNING_START = EVENT_USER_RUNNING | EVENT_FLAG_START;
+ public static final int EVENT_USER_RUNNING_FINISH = EVENT_USER_RUNNING | EVENT_FLAG_FINISH;
+ public static final int EVENT_USER_FOREGROUND_START =
+ EVENT_USER_FOREGROUND | EVENT_FLAG_START;
+ public static final int EVENT_USER_FOREGROUND_FINISH =
+ EVENT_USER_FOREGROUND | EVENT_FLAG_FINISH;
// For CMD_EVENT.
public int eventCode;
@@ -1269,11 +1309,11 @@
};
public static final String[] HISTORY_EVENT_NAMES = new String[] {
- "null", "proc", "fg", "top", "sync", "wake_lock_in"
+ "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg"
};
public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
- "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl"
+ "Enl", "Epr", "Efg", "Etp", "Esy", "Ewl", "Ejb", "Eur", "Euf"
};
/**
@@ -1857,9 +1897,9 @@
screenOnTime / 1000, phoneOnTime / 1000, wifiOnTime / 1000,
wifiRunningTime / 1000, bluetoothOnTime / 1000,
mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes,
- fullWakeLockTimeTotal, partialWakeLockTimeTotal,
- 0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which),
- getMobileRadioActiveAdjustedTime(which), interactiveTime / 1000,
+ fullWakeLockTimeTotal / 1000, partialWakeLockTimeTotal / 1000,
+ 0 /*legacy input event count*/, getMobileRadioActiveTime(rawRealtime, which) / 1000,
+ getMobileRadioActiveAdjustedTime(which) / 1000, interactiveTime / 1000,
lowPowerModeEnabledTime / 1000);
// Dump screen brightness stats
@@ -2080,10 +2120,9 @@
}
}
- Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
+ Map<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
if (wakelocks.size() > 0) {
- for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent
- : wakelocks.entrySet()) {
+ for (Map.Entry<String, ? extends Uid.Wakelock> ent : wakelocks.entrySet()) {
Uid.Wakelock wl = ent.getValue();
String linePrefix = "";
sb.setLength(0);
@@ -2105,6 +2144,32 @@
}
}
+ Map<String, ? extends Timer> syncs = u.getSyncStats();
+ if (syncs.size() > 0) {
+ for (Map.Entry<String, ? extends Timer> ent : syncs.entrySet()) {
+ Timer timer = ent.getValue();
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ int count = timer.getCountLocked(which);
+ if (totalTime != 0) {
+ dumpLine(pw, uid, category, SYNC_DATA, ent.getKey(), totalTime, count);
+ }
+ }
+ }
+
+ Map<String, ? extends Timer> jobs = u.getJobStats();
+ if (jobs.size() > 0) {
+ for (Map.Entry<String, ? extends Timer> ent : jobs.entrySet()) {
+ Timer timer = ent.getValue();
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ int count = timer.getCountLocked(which);
+ if (totalTime != 0) {
+ dumpLine(pw, uid, category, JOB_DATA, ent.getKey(), totalTime, count);
+ }
+ }
+ }
+
SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
int NSE = sensors.size();
for (int ise=0; ise<NSE; ise++) {
@@ -2937,8 +3002,7 @@
if (wakelocks.size() > 0) {
long totalFull = 0, totalPartial = 0, totalWindow = 0;
int count = 0;
- for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent
- : wakelocks.entrySet()) {
+ for (Map.Entry<String, ? extends Uid.Wakelock> ent : wakelocks.entrySet()) {
Uid.Wakelock wl = ent.getValue();
String linePrefix = ": ";
sb.setLength(0);
@@ -2998,6 +3062,56 @@
}
}
+ Map<String, ? extends Timer> syncs = u.getSyncStats();
+ if (syncs.size() > 0) {
+ for (Map.Entry<String, ? extends Timer> ent : syncs.entrySet()) {
+ Timer timer = ent.getValue();
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ int count = timer.getCountLocked(which);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Sync ");
+ sb.append(ent.getKey());
+ sb.append(": ");
+ if (totalTime != 0) {
+ formatTimeMs(sb, totalTime);
+ sb.append("realtime (");
+ sb.append(count);
+ sb.append(" times)");
+ } else {
+ sb.append("(not used)");
+ }
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
+ Map<String, ? extends Timer> jobs = u.getJobStats();
+ if (syncs.size() > 0) {
+ for (Map.Entry<String, ? extends Timer> ent : jobs.entrySet()) {
+ Timer timer = ent.getValue();
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ int count = timer.getCountLocked(which);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Job ");
+ sb.append(ent.getKey());
+ sb.append(": ");
+ if (totalTime != 0) {
+ formatTimeMs(sb, totalTime);
+ sb.append("realtime (");
+ sb.append(count);
+ sb.append(" times)");
+ } else {
+ sb.append("(not used)");
+ }
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
int NSE = sensors.size();
for (int ise=0; ise<NSE; ise++) {
@@ -3260,7 +3374,6 @@
int oldTemp = -1;
int oldVolt = -1;
long lastTime = -1;
- long firstTime = -1;
void reset() {
oldState = oldState2 = 0;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2687bcc..bfb5a26 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -824,6 +824,15 @@
public static final String ACTION_BATTERY_SAVER_SETTINGS
= "android.settings.BATTERY_SAVER_SETTINGS";
+ /**
+ * Activity Action: Show Home selection settings. If there are multiple activities
+ * that can satisfy the {@link Intent#CATEGORY_HOME} intent, this screen allows you
+ * to pick your preferred activity.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_HOME_SETTINGS
+ = "android.settings.HOME_SETTINGS";
+
// End of Intent actions for Settings
/**
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index c6005b9..363f97f 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -160,7 +160,7 @@
@Override
public TransitionSet setDuration(long duration) {
super.setDuration(duration);
- if (mDuration >= 0) {
+ if (mDuration >= 0 && mTransitions != null) {
int numTransitions = mTransitions.size();
for (int i = 0; i < numTransitions; ++i) {
mTransitions.get(i).setDuration(duration);
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 26f47f9..cc090ad 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -46,6 +46,12 @@
public static final int CLOCK_TICK = 4;
/**
+ * The user has pressed either a day or month or year date of a Calendar.
+ * @hide
+ */
+ public static final int CALENDAR_DATE = 5;
+
+ /**
* This is a private constant. Feel free to renumber as desired.
* @hide
*/
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index ae59bbc..a61d771 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -80,7 +80,7 @@
void removeWindowToken(IBinder token);
void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
- int configChanges, boolean voiceInteraction);
+ int configChanges, boolean voiceInteraction, boolean launchTaskBehind);
void setAppGroupId(IBinder token, int groupId);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index fb8ce15..e2ebf6e 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -81,6 +81,9 @@
// applied as translation when updating the root render node.
private int mInsetTop, mInsetLeft;
+ // Whether the surface has insets. Used to protect opacity.
+ private boolean mHasInsets;
+
// Light and shadow properties specified by the theme.
private final float mLightY;
private final float mLightZ;
@@ -187,12 +190,17 @@
final float lightX = width / 2.0f;
mWidth = width;
mHeight = height;
- if (surfaceInsets != null) {
+ if (surfaceInsets != null && !surfaceInsets.isEmpty()) {
+ mHasInsets = true;
mInsetLeft = surfaceInsets.left;
mInsetTop = surfaceInsets.top;
mSurfaceWidth = width + mInsetLeft + surfaceInsets.right;
mSurfaceHeight = height + mInsetTop + surfaceInsets.bottom;
+
+ // If the surface has insets, it can't be opaque.
+ setOpaque(false);
} else {
+ mHasInsets = false;
mInsetLeft = 0;
mInsetTop = 0;
mSurfaceWidth = width;
@@ -204,7 +212,7 @@
@Override
void setOpaque(boolean opaque) {
- nSetOpaque(mNativeProxy, opaque);
+ nSetOpaque(mNativeProxy, opaque && !mHasInsets);
}
@Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7fab808..b554548 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -461,6 +461,11 @@
}
}
+ // 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);
+
CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();
mTranslator = compatibilityInfo.getTranslator();
mDisplayAdjustments.setActivityToken(attrs.token);
@@ -1713,8 +1718,8 @@
if (hwInitialized ||
mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
- final Rect shadowInsets = params != null ? params.shadowInsets : null;
- mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets);
+ final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
+ mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, surfaceInsets);
if (!hwInitialized) {
mAttachInfo.mHardwareRenderer.invalidate(mSurface);
mFullRedrawNeeded = true;
@@ -2371,7 +2376,7 @@
}
final WindowManager.LayoutParams params = mWindowAttributes;
- final Rect surfaceInsets = params != null ? params.shadowInsets : null;
+ final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
@@ -3155,7 +3160,7 @@
mFullRedrawNeeded = true;
try {
final WindowManager.LayoutParams lp = mWindowAttributes;
- final Rect surfaceInsets = lp != null ? lp.shadowInsets : null;
+ final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
mWidth, mHeight, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index aa71ed8..c169d35 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -23,6 +23,8 @@
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -157,7 +159,7 @@
private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw";
private final Context mContext;
-
+
private TypedArray mWindowStyle;
private Callback mCallback;
private OnWindowDismissedCallback mOnWindowDismissedCallback;
@@ -181,7 +183,7 @@
private int mDefaultWindowFormat = PixelFormat.OPAQUE;
private boolean mHasSoftInputMode = false;
-
+
private boolean mDestroyed;
// The current window attributes.
@@ -227,7 +229,7 @@
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent event);
-
+
/**
* Called to process trackball events. At the very least your
* implementation must call
@@ -313,14 +315,14 @@
* Called when a panel's menu is opened by the user. This may also be
* called when the menu is changing from one type to another (for
* example, from the icon menu to the expanded menu).
- *
+ *
* @param featureId The panel that the menu is in.
* @param menu The menu that is opened.
* @return Return true to allow the menu to open, or false to prevent
* the menu from opening.
*/
public boolean onMenuOpened(int featureId, Menu menu);
-
+
/**
* Called when a panel's menu item has been selected by the user.
*
@@ -364,31 +366,31 @@
* for more information.
*/
public void onAttachedToWindow();
-
+
/**
* Called when the window has been attached to the window manager.
* See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()}
* for more information.
*/
public void onDetachedFromWindow();
-
+
/**
* Called when a panel is being closed. If another logical subsequent
* panel is being opened (and this panel is being closed to make room for the subsequent
* panel), this method will NOT be called.
- *
+ *
* @param featureId The panel that is being displayed.
* @param menu If onCreatePanelView() returned null, this is the Menu
* being displayed in the panel.
*/
public void onPanelClosed(int featureId, Menu menu);
-
+
/**
* Called when the user signals the desire to start a search.
- *
+ *
* @return true if search launched, false if activity refuses (blocks)
- *
- * @see android.app.Activity#onSearchRequested()
+ *
+ * @see android.app.Activity#onSearchRequested()
*/
public boolean onSearchRequested();
@@ -457,7 +459,7 @@
return mWindowStyle;
}
}
-
+
/**
* Set the container for this window. If not set, the DecorWindow
* operates as a top-level window; otherwise, it negotiates with the
@@ -488,7 +490,7 @@
public final boolean hasChildren() {
return mHasChildren;
}
-
+
/** @hide */
public final void destroy() {
mDestroyed = true;
@@ -622,14 +624,14 @@
* callback will be used to tell you about state changes to the surface.
*/
public abstract void takeSurface(SurfaceHolder.Callback2 callback);
-
+
/**
* Take ownership of this window's InputQueue. The window will no
* longer read and dispatch input events from the queue; it is your
* responsibility to do so.
*/
public abstract void takeInputQueue(InputQueue.Callback callback);
-
+
/**
* Return whether this window is being displayed with a floating style
* (based on the {@link android.R.attr#windowIsFloating} attribute in
@@ -740,7 +742,7 @@
}
dispatchWindowAttributesChanged(attrs);
}
-
+
/**
* Convenience function to set the flag bits as specified in flags, as
* per {@link #setFlags}.
@@ -756,7 +758,7 @@
public void addPrivateFlags(int flags) {
setPrivateFlags(flags, flags);
}
-
+
/**
* Convenience function to clear the flag bits as specified in flags, as
* per {@link #setFlags}.
@@ -772,7 +774,7 @@
* Set the flags of the window, as per the
* {@link WindowManager.LayoutParams WindowManager.LayoutParams}
* flags.
- *
+ *
* <p>Note that some flags must be set before the window decoration is
* created (by the first call to
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} or
@@ -859,20 +861,20 @@
protected final int getForcedWindowFlags() {
return mForcedWindowFlags;
}
-
+
/**
* Has the app specified their own soft input mode?
*/
protected final boolean hasSoftInputMode() {
return mHasSoftInputMode;
}
-
+
/** @hide */
public void setCloseOnTouchOutside(boolean close) {
mCloseOnTouchOutside = close;
mSetCloseOnTouchOutside = true;
}
-
+
/** @hide */
public void setCloseOnTouchOutsideIfNotSet(boolean close) {
if (!mSetCloseOnTouchOutside) {
@@ -880,10 +882,10 @@
mSetCloseOnTouchOutside = true;
}
}
-
+
/** @hide */
public abstract void alwaysReadCloseOnTouchAttr();
-
+
/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
@@ -892,7 +894,7 @@
}
return false;
}
-
+
private boolean isOutOfBounds(Context context, MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
@@ -902,7 +904,7 @@
|| (x > (decorView.getWidth()+slop))
|| (y > (decorView.getHeight()+slop));
}
-
+
/**
* Enable extended screen features. This must be called before
* setContentView(). May be called as many times as desired as long as it
@@ -989,7 +991,7 @@
* of the window that can not, from this point forward, be changed: the
* features that have been requested with {@link #requestFeature(int)},
* and certain window flags as described in {@link #setFlags(int, int)}.
- *
+ *
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
@@ -1037,7 +1039,7 @@
public abstract void togglePanel(int featureId, KeyEvent event);
public abstract void invalidatePanelMenu(int featureId);
-
+
public abstract boolean performPanelShortcut(int featureId,
int keyCode,
KeyEvent event,
@@ -1052,17 +1054,17 @@
/**
* Should be called when the configuration is changed.
- *
+ *
* @param newConfig The new configuration.
*/
public abstract void onConfigurationChanged(Configuration newConfig);
-
+
/**
* Change the background of this window to a Drawable resource. Setting the
* background to null will make the window be opaque. To make the window
* transparent, you can use an empty drawable (for instance a ColorDrawable
* with the color 0 or the system drawable android:drawable/empty.)
- *
+ *
* @param resid The resource identifier of a drawable resource which will be
* installed as the new background.
*/
@@ -1173,7 +1175,7 @@
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
-
+
/**
* Used by custom windows, such as Dialog, to pass the trackball event
* further down the view hierarchy. Application developers should
@@ -1181,7 +1183,7 @@
*
*/
public abstract boolean superDispatchTrackballEvent(MotionEvent event);
-
+
/**
* Used by custom windows, such as Dialog, to pass the generic motion event
* further down the view hierarchy. Application developers should
@@ -1194,11 +1196,11 @@
* Retrieve the top-level window decor view (containing the standard
* window frame/decorations and the client's content inside of that), which
* can be added as a window to the window manager.
- *
+ *
* <p><em>Note that calling this function for the first time "locks in"
* various window characteristics as described in
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
- *
+ *
* @return Returns the top-level window decor view.
*/
public abstract View getDecorView();
@@ -1206,16 +1208,16 @@
/**
* Retrieve the current decor view, but only if it has already been created;
* otherwise returns null.
- *
+ *
* @return Returns the top-level window decor or null.
* @see #getDecorView
*/
public abstract View peekDecorView();
public abstract Bundle saveHierarchyState();
-
+
public abstract void restoreHierarchyState(Bundle savedInstanceState);
-
+
protected abstract void onActive();
/**
@@ -1233,10 +1235,10 @@
{
return mFeatures;
}
-
+
/**
* Query for the availability of a certain feature.
- *
+ *
* @param feature The feature ID to check
* @return true if the feature is enabled, false otherwise.
*/
@@ -1290,9 +1292,9 @@
* @param event the {@link android.view.KeyEvent} to use to help check.
*/
public abstract boolean isShortcutKey(int keyCode, KeyEvent event);
-
+
/**
- * @see android.app.Activity#setVolumeControlStream(int)
+ * @see android.app.Activity#setVolumeControlStream(int)
*/
public abstract void setVolumeControlStream(int streamType);
@@ -1302,6 +1304,19 @@
public abstract int getVolumeControlStream();
/**
+ * @see android.app.Activity#setMediaController(android.media.session.MediaController)
+ */
+ public void setMediaController(MediaController controller) {
+ }
+
+ /**
+ * @see android.app.Activity#getMediaController()
+ */
+ public MediaController getMediaController() {
+ return null;
+ }
+
+ /**
* Set extra options that will influence the UI for this window.
* @param uiOptions Flags specifying extra options for this window.
*/
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c06b5d8..034778f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1298,7 +1298,7 @@
*
* @hide
*/
- public Rect shadowInsets = new Rect();
+ public Rect surfaceInsets = new Rect();
/**
* The desired bitmap format. May be one of the constants in
@@ -1580,10 +1580,10 @@
out.writeInt(hasSystemUiListeners ? 1 : 0);
out.writeInt(inputFeatures);
out.writeLong(userActivityTimeout);
- out.writeInt(shadowInsets.left);
- out.writeInt(shadowInsets.top);
- out.writeInt(shadowInsets.right);
- out.writeInt(shadowInsets.bottom);
+ out.writeInt(surfaceInsets.left);
+ out.writeInt(surfaceInsets.top);
+ out.writeInt(surfaceInsets.right);
+ out.writeInt(surfaceInsets.bottom);
}
public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -1626,7 +1626,10 @@
hasSystemUiListeners = in.readInt() != 0;
inputFeatures = in.readInt();
userActivityTimeout = in.readLong();
- shadowInsets.set(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+ surfaceInsets.left = in.readInt();
+ surfaceInsets.top = in.readInt();
+ surfaceInsets.right = in.readInt();
+ surfaceInsets.bottom = in.readInt();
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -1658,7 +1661,7 @@
/** {@hide} */
public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19;
/** {@hide} */
- public static final int SHADOW_INSETS_CHANGED = 1<<20;
+ public static final int SURFACE_INSETS_CHANGED = 1<<20;
/** {@hide} */
public static final int EVERYTHING_CHANGED = 0xffffffff;
@@ -1794,9 +1797,9 @@
changes |= USER_ACTIVITY_TIMEOUT_CHANGED;
}
- if (!shadowInsets.equals(o.shadowInsets)) {
- shadowInsets.set(o.shadowInsets);
- changes |= SHADOW_INSETS_CHANGED;
+ if (!surfaceInsets.equals(o.surfaceInsets)) {
+ surfaceInsets.set(o.surfaceInsets);
+ changes |= SURFACE_INSETS_CHANGED;
}
return changes;
@@ -1898,8 +1901,8 @@
if (userActivityTimeout >= 0) {
sb.append(" userActivityTimeout=").append(userActivityTimeout);
}
- if (!shadowInsets.equals(Insets.NONE)) {
- sb.append(" shadowInsets=").append(shadowInsets);
+ if (!surfaceInsets.equals(Insets.NONE)) {
+ sb.append(" surfaceInsets=").append(surfaceInsets);
}
sb.append('}');
return sb.toString();
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 9701c6f..aa0b94f 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -36,7 +36,6 @@
import android.util.AttributeSet;
import android.util.Log;
import android.util.LongSparseArray;
-import android.util.MathUtils;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.StateSet;
@@ -61,8 +60,6 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.inputmethod.BaseInputConnection;
@@ -7269,290 +7266,4 @@
}
}
}
-
- /**
- * Abstract position scroller that handles sub-position scrolling but has no
- * understanding of layout.
- */
- abstract class AbsSubPositionScroller extends AbsPositionScroller {
- private static final int DURATION_AUTO = -1;
-
- private static final int DURATION_AUTO_MIN = 100;
- private static final int DURATION_AUTO_MAX = 500;
-
- private final SubScroller mSubScroller = new SubScroller();
-
- /**
- * The target offset in pixels between the top of the list and the top
- * of the target position.
- */
- private int mOffset;
-
- /**
- * Scroll the minimum amount to get the target view entirely on-screen.
- */
- private void scrollToPosition(final int targetPosition, final boolean useOffset,
- final int offset, final int boundPosition, final int duration) {
- stop();
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- mPositionScrollAfterLayout = new Runnable() {
- @Override
- public void run() {
- scrollToPosition(
- targetPosition, useOffset, offset, boundPosition, duration);
- }
- };
- return;
- }
-
- if (mAdapter == null) {
- // Can't scroll anywhere without an adapter.
- return;
- }
-
- final int itemCount = getCount();
- final int clampedPosition = MathUtils.constrain(targetPosition, 0, itemCount - 1);
- final int clampedBoundPosition = MathUtils.constrain(boundPosition, -1, itemCount - 1);
- final int firstPosition = getFirstVisiblePosition();
- final int lastPosition = firstPosition + getChildCount();
- final int targetRow = getRowForPosition(clampedPosition);
- final int firstRow = getRowForPosition(firstPosition);
- final int lastRow = getRowForPosition(lastPosition);
- if (useOffset || targetRow <= firstRow) {
- // Offset so the target row is top-aligned.
- mOffset = offset;
- } else if (targetRow >= lastRow - 1) {
- // Offset so the target row is bottom-aligned.
- final int listHeight = getHeight() - getPaddingTop() - getPaddingBottom();
- mOffset = getHeightForPosition(clampedPosition) - listHeight;
- } else {
- // Don't scroll, target is entirely on-screen.
- return;
- }
-
- float endSubRow = targetRow;
- if (clampedBoundPosition != INVALID_POSITION) {
- final int boundRow = getRowForPosition(clampedBoundPosition);
- if (boundRow >= firstRow && boundRow < lastRow && boundRow != targetRow) {
- endSubRow = computeBoundSubRow(targetRow, boundRow);
- }
- }
-
- final View firstChild = getChildAt(0);
- if (firstChild == null) {
- return;
- }
-
- final int firstChildHeight = firstChild.getHeight();
- final float startOffsetRatio;
- if (firstChildHeight == 0) {
- startOffsetRatio = 0;
- } else {
- startOffsetRatio = -firstChild.getTop() / (float) firstChildHeight;
- }
-
- final float startSubRow = MathUtils.constrain(
- firstRow + startOffsetRatio, 0, getCount());
- if (startSubRow == endSubRow && mOffset == 0) {
- // Don't scroll, target is already in position.
- return;
- }
-
- final int durationMillis;
- if (duration == DURATION_AUTO) {
- final float subRowDelta = Math.abs(startSubRow - endSubRow);
- durationMillis = (int) MathUtils.lerp(
- DURATION_AUTO_MIN, DURATION_AUTO_MAX, subRowDelta / getCount());
- } else {
- durationMillis = duration;
- }
-
- mSubScroller.startScroll(startSubRow, endSubRow, durationMillis);
-
- postOnAnimation(mAnimationFrame);
- }
-
- /**
- * Given a target row and offset, computes the sub-row position that
- * aligns with the top of the list. If the offset is negative, the
- * resulting sub-row will be smaller than the target row.
- */
- private float resolveOffset(int targetRow, int offset) {
- // Compute the target sub-row position by finding the actual row
- // indicated by the target and offset.
- int remainingOffset = offset;
- int targetHeight = getHeightForRow(targetRow);
- if (offset < 0) {
- // Subtract row heights until we find the right row.
- while (targetRow > 0 && remainingOffset < 0) {
- remainingOffset += targetHeight;
- targetRow--;
- targetHeight = getHeightForRow(targetRow);
- }
- } else if (offset > 0) {
- // Add row heights until we find the right row.
- while (targetRow < getCount() - 1 && remainingOffset > targetHeight) {
- remainingOffset -= targetHeight;
- targetRow++;
- targetHeight = getHeightForRow(targetRow);
- }
- }
-
- final float targetOffsetRatio;
- if (remainingOffset < 0 || targetHeight == 0) {
- targetOffsetRatio = 0;
- } else {
- targetOffsetRatio = remainingOffset / (float) targetHeight;
- }
-
- return targetRow + targetOffsetRatio;
- }
-
- private float computeBoundSubRow(int targetRow, int boundRow) {
- final float targetSubRow = resolveOffset(targetRow, mOffset);
- mOffset = 0;
-
- // The target row is below the bound row, so the end position would
- // push the bound position above the list. Abort!
- if (targetSubRow >= boundRow) {
- return boundRow;
- }
-
- // Compute the closest possible sub-position that wouldn't push the
- // bound position's view further below the list.
- final int listHeight = getHeight() - getPaddingTop() - getPaddingBottom();
- final int boundHeight = getHeightForRow(boundRow);
- final float boundSubRow = resolveOffset(boundRow, -listHeight + boundHeight);
-
- return Math.max(boundSubRow, targetSubRow);
- }
-
- @Override
- public void start(int position) {
- scrollToPosition(position, false, 0, INVALID_POSITION, DURATION_AUTO);
- }
-
- @Override
- public void start(int position, int boundPosition) {
- scrollToPosition(position, false, 0, boundPosition, DURATION_AUTO);
- }
-
- @Override
- public void startWithOffset(int position, int offset) {
- scrollToPosition(position, true, offset, INVALID_POSITION, DURATION_AUTO);
- }
-
- @Override
- public void startWithOffset(int position, int offset, int duration) {
- scrollToPosition(position, true, offset, INVALID_POSITION, duration);
- }
-
- @Override
- public void stop() {
- removeCallbacks(mAnimationFrame);
- }
-
- /**
- * Returns the height of a row, which is computed as the maximum height of
- * the items in the row.
- *
- * @param row the row index
- * @return row height in pixels
- */
- public abstract int getHeightForRow(int row);
-
- /**
- * Returns the row for the specified item position.
- *
- * @param position the item position
- * @return the row index
- */
- public abstract int getRowForPosition(int position);
-
- /**
- * Returns the first item position within the specified row.
- *
- * @param row the row
- * @return the position of the first item in the row
- */
- public abstract int getFirstPositionForRow(int row);
-
- private void onAnimationFrame() {
- final boolean shouldPost = mSubScroller.computePosition();
- final float subRow = mSubScroller.getPosition();
-
- final int row = (int) subRow;
- final int position = getFirstPositionForRow(row);
- if (position >= getCount()) {
- // Invalid position, abort scrolling.
- return;
- }
-
- final int rowHeight = getHeightForRow(row);
- final int offset = (int) (rowHeight * (subRow - row));
- final int addOffset = (int) (mOffset * mSubScroller.getInterpolatedValue());
- setSelectionFromTop(position, -offset - addOffset);
-
- if (shouldPost) {
- postOnAnimation(mAnimationFrame);
- }
- }
-
- private Runnable mAnimationFrame = new Runnable() {
- @Override
- public void run() {
- onAnimationFrame();
- }
- };
- }
-
- /**
- * Scroller capable of returning floating point positions.
- */
- static class SubScroller {
- private static final Interpolator INTERPOLATOR = new AccelerateDecelerateInterpolator();
-
- private float mStartPosition;
- private float mEndPosition;
- private long mStartTime;
- private long mDuration;
-
- private float mPosition;
- private float mInterpolatedValue;
-
- public void startScroll(float startPosition, float endPosition, int duration) {
- mStartPosition = startPosition;
- mEndPosition = endPosition;
- mDuration = duration;
-
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mPosition = startPosition;
- mInterpolatedValue = 0;
- }
-
- public boolean computePosition() {
- final long elapsed = AnimationUtils.currentAnimationTimeMillis() - mStartTime;
- final float value;
- if (mDuration <= 0) {
- value = 1;
- } else {
- value = MathUtils.constrain(elapsed / (float) mDuration, 0, 1);
- }
-
- mInterpolatedValue = INTERPOLATOR.getInterpolation(value);
- mPosition = (mEndPosition - mStartPosition) * mInterpolatedValue + mStartPosition;
-
- return elapsed < mDuration;
- }
-
- public float getPosition() {
- return mPosition;
- }
-
- public float getInterpolatedValue() {
- return mInterpolatedValue;
- }
- }
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 2c1a77c..8f49fb8 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -17,7 +17,9 @@
package android.widget;
import android.annotation.Widget;
+import android.app.UiModeManager;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Parcel;
@@ -49,6 +51,8 @@
import libcore.icu.ICU;
+import static android.os.Build.VERSION_CODES.L;
+
/**
* This class is a widget for selecting a date. The date can be selected by a
* year, month, and day spinners or a {@link CalendarView}. The set of spinners
@@ -70,6 +74,15 @@
* @attr ref android.R.styleable#DatePicker_minDate
* @attr ref android.R.styleable#DatePicker_spinnersShown
* @attr ref android.R.styleable#DatePicker_calendarViewShown
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekBackgroundColor
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorBackgroundColor
+ * @attr ref android.R.styleable#DatePicker_dateSelectorMonthTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfMonthTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearListItemTextAppearance
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearListSelectedCircleColor
+ * @attr ref android.R.styleable#DatePicker_calendarTextColor
*/
@Widget
public class DatePicker extends FrameLayout {
@@ -78,6 +91,10 @@
private DatePickerDelegate mDelegate;
+ private int mDefStyleAttr;
+ private int mDefStyleRes;
+ private Context mContext;
+
/**
* The callback used to indicate the user changes\d the date.
*/
@@ -110,7 +127,65 @@
public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mDelegate = new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
+ mContext = context;
+ mDefStyleAttr = defStyleAttr;
+ mDefStyleRes = defStyleRes;
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
+ mDefStyleAttr, mDefStyleRes);
+
+ // Create the correct UI delegate. Default is the legacy one.
+ final boolean isLegacyMode = a.getBoolean(R.styleable.DatePicker_legacyMode,
+ isLegacyMode());
+
+ a.recycle();
+
+ setLegacyMode(isLegacyMode, attrs);
+ }
+
+ private boolean isLegacyMode() {
+ UiModeManager uiModeManager =
+ (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+ if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
+ return true;
+ }
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ return targetSdkVersion < L;
+ }
+
+ private DatePickerDelegate createLegacyUIDelegate(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ return new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private DatePickerDelegate createNewUIDelegate(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ return new android.widget.DatePickerDelegate(this, context, attrs, defStyleAttr,
+ defStyleRes);
+ }
+
+ /**
+ * @hide
+ */
+ public void setLegacyMode(boolean isLegacyMode, AttributeSet attrs) {
+ removeAllViewsInLayout();
+ mDelegate = isLegacyMode ?
+ createLegacyUIDelegate(mContext, attrs, mDefStyleAttr, mDefStyleRes) :
+ createNewUIDelegate(mContext, attrs, mDefStyleAttr, mDefStyleRes);
+ }
+
+ /**
+ * @hide
+ */
+ public void setShowDoneButton(boolean showDoneButton) {
+ mDelegate.setShowDoneButton(showDoneButton);
+ }
+
+ /**
+ * @hide
+ */
+ public void setDismissCallback(DatePickerDismissCallback callback) {
+ mDelegate.setDismissCallback(callback);
}
/**
@@ -129,7 +204,7 @@
}
/**
- * Updates the current date.
+ * Update the current date.
*
* @param year The year.
* @param month The month which is <strong>starting from zero</strong>.
@@ -171,7 +246,7 @@
* @return The minimal supported date.
*/
public long getMinDate() {
- return mDelegate.getMinDate();
+ return mDelegate.getMinDate().getTimeInMillis();
}
/**
@@ -196,7 +271,7 @@
* @return The maximal supported date.
*/
public long getMaxDate() {
- return mDelegate.getMaxDate();
+ return mDelegate.getMaxDate().getTimeInMillis();
}
/**
@@ -300,6 +375,182 @@
mDelegate.setSpinnersShown(shown);
}
+ /**
+ * Sets the background color for the date selector's day of week.
+ *
+ * @param color The background color.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekBackgroundColor
+ */
+ public void setDateSelectorDayOfWeekBackgroundColor(int color) {
+ mDelegate.setDateSelectorDayOfWeekBackgroundColor(color);
+ }
+
+ /**
+ * Gets the background color for the date selector's day of week.
+ *
+ * @return The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekBackgroundColor
+ */
+ public int getDateSelectorDayOfWeekBackgroundColor() {
+ return mDelegate.getDateSelectorDayOfWeekBackgroundColor();
+ }
+
+ /**
+ * Sets the text appearance for the date selector's day of week.
+ *
+ * @param resId The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekTextAppearance
+ */
+ public void setDateSelectorDayOfWeekTextAppearance(int resId) {
+ mDelegate.setDateSelectorDayOfWeekTextAppearance(resId);
+ }
+
+ /**
+ * Gets the text appearance for the date selector's day of week.
+ *
+ * @return The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfWeekTextAppearance
+ */
+ public int getDateSelectorDayOfWeekTextAppearance() {
+ return mDelegate.getDateSelectorDayOfWeekTextAppearance();
+ }
+
+ /**
+ * Sets the background color for the date selector's.
+ *
+ * @param color The background color.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorBackgroundColor
+ */
+ public void setDateSelectorBackgroundColor(int color) {
+ mDelegate.setDateSelectorBackgroundColor(color);
+ }
+
+ /**
+ * Gets the background color for the date selector's.
+ *
+ * @return The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorBackgroundColor
+ */
+ public int getDateSelectorBackgroundColor() {
+ return mDelegate.getDateSelectorBackgroundColor();
+ }
+
+ /**
+ * Sets the text appearance for the date selector's month.
+ *
+ * @param resId The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorMonthTextAppearance
+ */
+ public void setDateSelectorMonthTextAppearance(int resId) {
+ mDelegate.setDateSelectorMonthTextAppearance(resId);
+ }
+
+ /**
+ * Gets the text appearance for the date selector's month.
+ *
+ * @return The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorMonthTextAppearance
+ */
+ public int getDateSelectorMonthTextAppearance() {
+ return mDelegate.getDateSelectorMonthTextAppearance();
+ }
+
+ /**
+ * Sets the text appearance for the date selector's day of month.
+ *
+ * @param resId The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfMonthTextAppearance
+ */
+ public void setDateSelectorDayOfMonthTextAppearance(int resId) {
+ mDelegate.setDateSelectorDayOfMonthTextAppearance(resId);
+ }
+
+ /**
+ * Gets the text appearance for the date selector's day of month.
+ *
+ * @return The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorDayOfMonthTextAppearance
+ */
+ public int getDateSelectorDayOfMonthTextAppearance() {
+ return mDelegate.getDateSelectorDayOfMonthTextAppearance();
+ }
+
+ /**
+ * Sets the text appearance for the date selector's year.
+ *
+ * @param resId The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearTextAppearance
+ */
+ public void setDateSelectorYearTextAppearance(int resId) {
+ mDelegate.setDateSelectorYearTextAppearance(resId);
+ }
+
+ /**
+ * Gets the text appearance for the date selector's year.
+ *
+ * @return The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearTextAppearance
+ */
+ public int getDateSelectorYearTextAppearance() {
+ return mDelegate.getDateSelectorYearTextAppearance();
+ }
+
+ /**
+ * Sets the text appearance for the year list item.
+ *
+ * @param resId The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearListItemTextAppearance
+ */
+ public void setDateSelectorYearListItemTextAppearance(int resId) {
+ mDelegate.setDateSelectorYearListItemTextAppearance(resId);
+ }
+
+ /**
+ * Gets the text appearance for the year list item.
+ *
+ * @return The text appearance resource id.
+ *
+ * @attr ref android.R.styleable#DatePicker_dateSelectorYearListItemTextAppearance
+ */
+ public int getDateSelectorYearListItemTextAppearance() {
+ return mDelegate.getDateSelectorYearListItemTextAppearance();
+ }
+
+ /**
+ * Sets the text color state list for the calendar.
+ *
+ * @param colors The text color state list.
+ *
+ * @attr ref android.R.styleable#DatePicker_calendarTextColor
+ */
+ public void setCalendarTextColor(ColorStateList colors) {
+ mDelegate.setCalendarTextColor(colors);
+ }
+
+ /**
+ * Gets the text color state list for the calendar.
+ *
+ * @return The text color state list for the calendar.
+ *
+ * @attr ref android.R.styleable#DatePicker_calendarTextColor
+ */
+ public ColorStateList getCalendarTextColor() {
+ return mDelegate.getCalendarTextColors();
+ }
+
// Override so we are in complete control of save / restore for this widget.
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
@@ -323,6 +574,8 @@
* A delegate interface that defined the public API of the DatePicker. Allows different
* DatePicker implementations. This would need to be implemented by the DatePicker delegates
* for the real behavior.
+ *
+ * @hide
*/
interface DatePickerDelegate {
void init(int year, int monthOfYear, int dayOfMonth,
@@ -335,15 +588,42 @@
int getDayOfMonth();
void setMinDate(long minDate);
- long getMinDate();
+ Calendar getMinDate();
void setMaxDate(long maxDate);
- long getMaxDate();
+ Calendar getMaxDate();
void setEnabled(boolean enabled);
boolean isEnabled();
- CalendarView getCalendarView ();
+ void setDateSelectorDayOfWeekBackgroundColor(int color);
+ int getDateSelectorDayOfWeekBackgroundColor();
+
+ void setDateSelectorDayOfWeekTextAppearance(int resId);
+ int getDateSelectorDayOfWeekTextAppearance();
+
+ void setDateSelectorBackgroundColor(int color);
+ int getDateSelectorBackgroundColor();
+
+ void setDateSelectorMonthTextAppearance(int resId);
+ int getDateSelectorMonthTextAppearance();
+
+ void setDateSelectorDayOfMonthTextAppearance(int resId);
+ int getDateSelectorDayOfMonthTextAppearance();
+
+ void setDateSelectorYearTextAppearance(int resId);
+ int getDateSelectorYearTextAppearance();
+
+ void setDateSelectorYearListItemTextAppearance(int resId);
+ int getDateSelectorYearListItemTextAppearance();
+
+ void setDateSelectorYearListSelectedCircleColor(int color);
+ int getDateSelectorYearListSelectedCircleColor();
+
+ void setCalendarTextColor(ColorStateList colors);
+ ColorStateList getCalendarTextColors();
+
+ CalendarView getCalendarView();
void setCalendarViewShown(boolean shown);
boolean getCalendarViewShown();
@@ -351,6 +631,9 @@
void setSpinnersShown(boolean shown);
boolean getSpinnersShown();
+ void setShowDoneButton(boolean showDoneButton);
+ void setDismissCallback(DatePickerDismissCallback callback);
+
void onConfigurationChanged(Configuration newConfig);
void dispatchRestoreInstanceState(SparseArray<Parcelable> container);
@@ -366,7 +649,7 @@
/**
* An abstract class which can be used as a start for DatePicker implementations
*/
- abstract static class AbstractTimePickerDelegate implements DatePickerDelegate {
+ abstract static class AbstractDatePickerDelegate implements DatePickerDelegate {
// The delegator
protected DatePicker mDelegator;
@@ -379,7 +662,7 @@
// Callbacks
protected OnDateChangedListener mOnDateChangedListener;
- public AbstractTimePickerDelegate(DatePicker delegator, Context context) {
+ public AbstractDatePickerDelegate(DatePicker delegator, Context context) {
mDelegator = delegator;
mContext = context;
@@ -396,9 +679,18 @@
}
/**
+ * A callback interface for dismissing the DatePicker when included into a Dialog
+ *
+ * @hide
+ */
+ public static interface DatePickerDismissCallback {
+ void dismiss(DatePicker view, boolean isCancel, int year, int month, int dayOfMonth);
+ }
+
+ /**
* A delegate implementing the basic DatePicker
*/
- private static class LegacyDatePickerDelegate extends AbstractTimePickerDelegate {
+ private static class LegacyDatePickerDelegate extends AbstractDatePickerDelegate {
private static final String DATE_FORMAT = "MM/dd/yyyy";
@@ -466,7 +758,7 @@
String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
int layoutResourceId = attributesArray.getResourceId(
- R.styleable.DatePicker_internalLayout, R.layout.date_picker);
+ R.styleable.DatePicker_legacyLayout, R.layout.date_picker_legacy);
attributesArray.recycle();
LayoutInflater inflater = (LayoutInflater) context
@@ -643,8 +935,10 @@
}
@Override
- public long getMinDate() {
- return mCalendarView.getMinDate();
+ public Calendar getMinDate() {
+ final Calendar minDate = Calendar.getInstance();
+ minDate.setTimeInMillis(mCalendarView.getMinDate());
+ return minDate;
}
@Override
@@ -664,8 +958,10 @@
}
@Override
- public long getMaxDate() {
- return mCalendarView.getMaxDate();
+ public Calendar getMaxDate() {
+ final Calendar maxDate = Calendar.getInstance();
+ maxDate.setTimeInMillis(mCalendarView.getMaxDate());
+ return maxDate;
}
@Override
@@ -683,6 +979,87 @@
}
@Override
+ public void setDateSelectorDayOfWeekBackgroundColor(int color) {
+ }
+
+ @Override
+ public int getDateSelectorDayOfWeekBackgroundColor() {
+ return 0;
+ }
+
+ @Override
+ public void setDateSelectorDayOfWeekTextAppearance(int resId) {
+ }
+
+ @Override
+ public int getDateSelectorDayOfWeekTextAppearance() {
+ return 0;
+ }
+
+ @Override
+ public void setDateSelectorBackgroundColor(int color) {
+ }
+
+ @Override
+ public int getDateSelectorBackgroundColor() {
+ return 0;
+ }
+
+ @Override
+ public void setDateSelectorMonthTextAppearance(int resId) {
+ }
+
+ @Override
+ public int getDateSelectorMonthTextAppearance() {
+ return 0;
+ }
+
+ @Override
+ public void setDateSelectorDayOfMonthTextAppearance(int resId) {
+ }
+
+ @Override
+ public int getDateSelectorDayOfMonthTextAppearance() {
+ return 0;
+ }
+
+ @Override
+ public void setDateSelectorYearTextAppearance(int resId) {
+ }
+
+ @Override
+ public int getDateSelectorYearTextAppearance() {
+ return 0;
+ }
+
+ @Override
+ public void setDateSelectorYearListItemTextAppearance(int resId) {
+ }
+
+ @Override
+ public int getDateSelectorYearListItemTextAppearance() {
+ return 0;
+ }
+
+ @Override
+ public void setDateSelectorYearListSelectedCircleColor(int color) {
+ }
+
+ @Override
+ public int getDateSelectorYearListSelectedCircleColor() {
+ return 0;
+ }
+
+ @Override
+ public void setCalendarTextColor(ColorStateList colors) {
+ }
+
+ @Override
+ public ColorStateList getCalendarTextColors() {
+ return ColorStateList.valueOf(0);
+ }
+
+ @Override
public CalendarView getCalendarView() {
return mCalendarView;
}
@@ -708,6 +1085,16 @@
}
@Override
+ public void setShowDoneButton(boolean showDoneButton) {
+ // Nothing to do
+ }
+
+ @Override
+ public void setDismissCallback(DatePickerDismissCallback callback) {
+ // Nothing to do
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
setCurrentLocale(newConfig.locale);
}
diff --git a/core/java/android/widget/DatePickerController.java b/core/java/android/widget/DatePickerController.java
new file mode 100644
index 0000000..6a074da
--- /dev/null
+++ b/core/java/android/widget/DatePickerController.java
@@ -0,0 +1,56 @@
+/*
+ * 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 android.widget;
+
+import java.util.Calendar;
+
+/**
+ * Controller class to communicate among the various components of the date picker dialog.
+ *
+ * @hide
+ */
+interface DatePickerController {
+
+ void onYearSelected(int year);
+
+ void onDayOfMonthSelected(int year, int month, int day);
+
+ void registerOnDateChangedListener(OnDateChangedListener listener);
+
+ void unregisterOnDateChangedListener(OnDateChangedListener listener);
+
+ Calendar getSelectedDay();
+
+ int getFirstDayOfWeek();
+
+ int getMinYear();
+ int getMaxYear();
+
+ int getMinMonth();
+ int getMaxMonth();
+
+ int getMinDay();
+ int getMaxDay();
+
+ void setMinDate(long minDate);
+ Calendar getMinDate();
+
+ void setMaxDate(long maxDate);
+ Calendar getMaxDate();
+
+ void tryVibrate();
+}
diff --git a/core/java/android/widget/DatePickerDelegate.java b/core/java/android/widget/DatePickerDelegate.java
new file mode 100644
index 0000000..31044d4
--- /dev/null
+++ b/core/java/android/widget/DatePickerDelegate.java
@@ -0,0 +1,979 @@
+/*
+ * 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 android.widget;
+
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.HapticFeedbackConstants;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+
+import com.android.internal.R;
+import com.android.internal.widget.AccessibleDateAnimator;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+
+/**
+ * A delegate for picking up a date (day / month / year).
+ */
+class DatePickerDelegate extends DatePicker.AbstractDatePickerDelegate implements
+ View.OnClickListener, DatePickerController {
+
+ private static final int UNINITIALIZED = -1;
+ private static final int MONTH_AND_DAY_VIEW = 0;
+ private static final int YEAR_VIEW = 1;
+
+ private static final int DEFAULT_START_YEAR = 1900;
+ private static final int DEFAULT_END_YEAR = 2100;
+
+ private static final int PULSE_ANIMATOR_DURATION = 544;
+
+ private static final int ANIMATION_DURATION = 300;
+ private static final int ANIMATION_DELAY = 650;
+
+ private static final int MONTH_INDEX = 0;
+ private static final int DAY_INDEX = 1;
+ private static final int YEAR_INDEX = 2;
+
+ private SimpleDateFormat mYearFormat = new SimpleDateFormat("y", Locale.getDefault());
+ private SimpleDateFormat mDayFormat = new SimpleDateFormat("d", Locale.getDefault());
+
+ private TextView mDayOfWeekView;
+ private LinearLayout mDateLayout;
+ private LinearLayout mMonthAndDayLayout;
+ private TextView mSelectedMonthTextView;
+ private TextView mSelectedDayTextView;
+ private TextView mSelectedYearView;
+ private DayPickerView mDayPickerView;
+ private YearPickerView mYearPickerView;
+
+ private ViewGroup mLayoutButtons;
+
+ private boolean mIsEnabled = true;
+
+ // Accessibility strings.
+ private String mDayPickerDescription;
+ private String mSelectDay;
+ private String mYearPickerDescription;
+ private String mSelectYear;
+
+ private AccessibleDateAnimator mAnimator;
+
+ private DatePicker.OnDateChangedListener mDateChangedListener;
+
+ private boolean mDelayAnimation = true;
+
+ private int mCurrentView = UNINITIALIZED;
+
+ private Calendar mCurrentDate;
+ private Calendar mTempDate;
+ private Calendar mMinDate;
+ private Calendar mMaxDate;
+
+ // For showing the done button when in a Dialog
+ private Button mDoneButton;
+ private boolean mShowDoneButton;
+ private DatePicker.DatePickerDismissCallback mDismissCallback;
+
+ private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();
+
+ private int mDayOfWeekTextAppearanceResId;
+ private int mMonthTextAppearanceResId;
+ private int mDayOfMonthTextAppearanceResId;
+ private int mYearTextAppearanceResId;
+
+ private int mYearListItemTextAppearanceResId;
+
+ private int mDayOfWeekBackgroundColor;
+ private int mMonthAndDayBackgroundColor;
+
+ private ColorStateList mCalendarTextColors;
+
+ public DatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+
+ super(delegator, context);
+
+ final Locale locale = Locale.getDefault();
+ mMinDate = getCalendarForLocale(mMinDate, locale);
+ mMaxDate = getCalendarForLocale(mMaxDate, locale);
+ mTempDate = getCalendarForLocale(mMaxDate, locale);
+
+ mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
+
+ mMinDate.set(DEFAULT_START_YEAR, 1, 1);
+ mMaxDate.set(DEFAULT_END_YEAR, 12, 31);
+
+ // process style attributes
+ final TypedArray a = mContext.obtainStyledAttributes(attrs,
+ R.styleable.DatePicker, defStyleAttr, defStyleRes);
+
+ final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ final int layoutResourceId = a.getResourceId(
+ R.styleable.DatePicker_internalLayout, R.layout.date_picker_holo);
+
+ View mainView = inflater.inflate(layoutResourceId, null);
+ mDelegator.addView(mainView);
+
+ mDayOfWeekView = (TextView) mainView.findViewById(R.id.date_picker_header);
+ mDateLayout = (LinearLayout) mainView.findViewById(R.id.day_picker_selector_layout);
+ mMonthAndDayLayout = (LinearLayout) mainView.findViewById(
+ R.id.date_picker_month_and_day_layout);
+ mMonthAndDayLayout.setOnClickListener(this);
+ mSelectedMonthTextView = (TextView) mainView.findViewById(R.id.date_picker_month);
+ mSelectedDayTextView = (TextView) mainView.findViewById(R.id.date_picker_day);
+ mSelectedYearView = (TextView) mainView.findViewById(R.id.date_picker_year);
+ mSelectedYearView.setOnClickListener(this);
+
+ // Use Theme attributes if possible
+ mDayOfWeekTextAppearanceResId = a.getResourceId(
+ R.styleable.DatePicker_dateSelectorDayOfWeekTextAppearance, -1);
+ if (mDayOfWeekTextAppearanceResId != -1) {
+ mDayOfWeekView.setTextAppearance(context, mDayOfWeekTextAppearanceResId);
+ }
+
+ mMonthTextAppearanceResId = a.getResourceId(
+ R.styleable.DatePicker_dateSelectorMonthTextAppearance, -1);
+ if (mMonthTextAppearanceResId != -1) {
+ mSelectedMonthTextView.setTextAppearance(context, mMonthTextAppearanceResId);
+ }
+
+ mDayOfMonthTextAppearanceResId = a.getResourceId(
+ R.styleable.DatePicker_dateSelectorDayOfMonthTextAppearance, -1);
+ if (mDayOfMonthTextAppearanceResId != -1) {
+ mSelectedDayTextView.setTextAppearance(context, mDayOfMonthTextAppearanceResId);
+ }
+
+ mYearTextAppearanceResId = a.getResourceId(
+ R.styleable.DatePicker_dateSelectorYearTextAppearance, -1);
+ if (mYearTextAppearanceResId != -1) {
+ mSelectedYearView.setTextAppearance(context, mYearTextAppearanceResId);
+ }
+
+ Resources res = mDelegator.getResources();
+
+ mDayOfWeekBackgroundColor = a.getColor(
+ R.styleable.DatePicker_dateSelectorDayOfWeekBackgroundColor,
+ res.getColor(
+ R.color.datepicker_default_header_dayofweek_background_color_holo_light));
+ mDayOfWeekView.setBackgroundColor(mDayOfWeekBackgroundColor);
+
+ mMonthAndDayBackgroundColor = a.getColor(R.styleable.DatePicker_dateSelectorBackgroundColor,
+ res.getColor(R.color.datepicker_default_header_selector_background_holo_light));
+ mMonthAndDayLayout.setBackgroundColor(mMonthAndDayBackgroundColor);
+
+ mDayPickerView = new DayPickerView(mContext, this);
+ mYearPickerView = new YearPickerView(mContext);
+ mYearPickerView.init(this);
+
+ ColorStateList colors = a.getColorStateList(R.styleable.DatePicker_calendarTextColor);
+ setCalendarTextColor(colors);
+
+ mDayPickerDescription = res.getString(R.string.day_picker_description);
+ mSelectDay = res.getString(R.string.select_day);
+ mYearPickerDescription = res.getString(R.string.year_picker_description);
+ mSelectYear = res.getString(R.string.select_year);
+
+ mAnimator = (AccessibleDateAnimator) mainView.findViewById(R.id.animator);
+ mAnimator.addView(mDayPickerView);
+ mAnimator.addView(mYearPickerView);
+ mAnimator.setDateMillis(mCurrentDate.getTimeInMillis());
+ Animation animation = new AlphaAnimation(0.0f, 1.0f);
+ animation.setDuration(ANIMATION_DURATION);
+ mAnimator.setInAnimation(animation);
+ Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
+ animation2.setDuration(ANIMATION_DURATION);
+ mAnimator.setOutAnimation(animation2);
+
+ mLayoutButtons = (ViewGroup) mainView.findViewById(R.id.layout_buttons);
+ mDoneButton = (Button) mainView.findViewById(R.id.done);
+ mDoneButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ if (mDismissCallback != null) {
+ mDismissCallback.dismiss(mDelegator, false, mCurrentDate.get(Calendar.YEAR),
+ mCurrentDate.get(Calendar.MONTH),
+ mCurrentDate.get(Calendar.DAY_OF_MONTH));
+ }
+ }
+ });
+
+ updateDisplay(false);
+ setCurrentView(MONTH_AND_DAY_VIEW);
+ }
+
+ /**
+ * Gets a calendar for locale bootstrapped with the value of a given calendar.
+ *
+ * @param oldCalendar The old calendar.
+ * @param locale The locale.
+ */
+ private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
+ if (oldCalendar == null) {
+ return Calendar.getInstance(locale);
+ } else {
+ final long currentTimeMillis = oldCalendar.getTimeInMillis();
+ Calendar newCalendar = Calendar.getInstance(locale);
+ newCalendar.setTimeInMillis(currentTimeMillis);
+ return newCalendar;
+ }
+ }
+
+ /**
+ * Compute the array representing the order of Month / Day / Year views in their layout.
+ * Will be used for I18N purpose as the order of them depends on the Locale.
+ */
+ private int[] getMonthDayYearIndexes(String pattern) {
+ int[] result = new int[3];
+
+ final String filteredPattern = pattern.replaceAll("'.*?'", "");
+
+ final int dayIndex = filteredPattern.indexOf('d');
+ final int monthMIndex = filteredPattern.indexOf("M");
+ final int monthIndex = (monthMIndex != -1) ? monthMIndex : filteredPattern.indexOf("L");
+ final int yearIndex = filteredPattern.indexOf("y");
+
+ if (yearIndex < monthIndex) {
+ result[YEAR_INDEX] = 0;
+
+ if (monthIndex < dayIndex) {
+ result[MONTH_INDEX] = 1;
+ result[DAY_INDEX] = 2;
+ } else {
+ result[MONTH_INDEX] = 2;
+ result[DAY_INDEX] = 1;
+ }
+ } else {
+ result[YEAR_INDEX] = 2;
+
+ if (monthIndex < dayIndex) {
+ result[MONTH_INDEX] = 0;
+ result[DAY_INDEX] = 1;
+ } else {
+ result[MONTH_INDEX] = 1;
+ result[DAY_INDEX] = 0;
+ }
+ }
+ return result;
+ }
+
+ private void updateDisplay(boolean announce) {
+ if (mDayOfWeekView != null) {
+ mDayOfWeekView.setText(mCurrentDate.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
+ Locale.getDefault()));
+ }
+ final String bestDateTimePattern =
+ DateFormat.getBestDateTimePattern(mCurrentLocale, "yMMMd");
+
+ // Compute indices of Month, Day and Year views
+ int[] viewIndices = getMonthDayYearIndexes(bestDateTimePattern);
+
+ // Restart from a clean state
+ mMonthAndDayLayout.removeAllViews();
+ mDateLayout.removeView(mSelectedYearView);
+
+ // Position the Year View at the correct location
+ if (viewIndices[YEAR_INDEX] == 0) {
+ mDateLayout.addView(mSelectedYearView, 0);
+ } else {
+ mDateLayout.addView(mSelectedYearView, 1);
+ }
+
+ // Position Day and Month Views
+ if (viewIndices[MONTH_INDEX] > viewIndices[DAY_INDEX]) {
+ // Day View is first
+ mMonthAndDayLayout.addView(mSelectedDayTextView);
+ mMonthAndDayLayout.addView(mSelectedMonthTextView);
+ } else {
+ // Month View is first
+ mMonthAndDayLayout.addView(mSelectedMonthTextView);
+ mMonthAndDayLayout.addView(mSelectedDayTextView);
+ }
+
+ mSelectedMonthTextView.setText(mCurrentDate.getDisplayName(Calendar.MONTH, Calendar.SHORT,
+ Locale.getDefault()).toUpperCase(Locale.getDefault()));
+ mSelectedDayTextView.setText(mDayFormat.format(mCurrentDate.getTime()));
+ mSelectedYearView.setText(mYearFormat.format(mCurrentDate.getTime()));
+
+ // Accessibility.
+ long millis = mCurrentDate.getTimeInMillis();
+ mAnimator.setDateMillis(millis);
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
+ String monthAndDayText = DateUtils.formatDateTime(mContext, millis, flags);
+ mMonthAndDayLayout.setContentDescription(monthAndDayText);
+
+ if (announce) {
+ flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
+ String fullDateText = DateUtils.formatDateTime(mContext, millis, flags);
+ mAnimator.announceForAccessibility(fullDateText);
+ }
+ updatePickers();
+ }
+
+ private void setCurrentView(final int viewIndex) {
+ long millis = mCurrentDate.getTimeInMillis();
+
+ switch (viewIndex) {
+ case MONTH_AND_DAY_VIEW:
+ ObjectAnimator pulseAnimator = getPulseAnimator(mMonthAndDayLayout, 0.9f,
+ 1.05f);
+ if (mDelayAnimation) {
+ pulseAnimator.setStartDelay(ANIMATION_DELAY);
+ mDelayAnimation = false;
+ }
+ mDayPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayLayout.setSelected(true);
+ mSelectedYearView.setSelected(false);
+ mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
+ mCurrentView = viewIndex;
+ }
+ pulseAnimator.start();
+
+ int flags = DateUtils.FORMAT_SHOW_DATE;
+ String dayString = DateUtils.formatDateTime(mContext, millis, flags);
+ mAnimator.setContentDescription(mDayPickerDescription + ": " + dayString);
+ mAnimator.announceForAccessibility(mSelectDay);
+ break;
+ case YEAR_VIEW:
+ pulseAnimator = getPulseAnimator(mSelectedYearView, 0.85f, 1.1f);
+ if (mDelayAnimation) {
+ pulseAnimator.setStartDelay(ANIMATION_DELAY);
+ mDelayAnimation = false;
+ }
+ mYearPickerView.onDateChanged();
+ if (mCurrentView != viewIndex) {
+ mMonthAndDayLayout.setSelected(false);
+ mSelectedYearView.setSelected(true);
+ mAnimator.setDisplayedChild(YEAR_VIEW);
+ mCurrentView = viewIndex;
+ }
+ pulseAnimator.start();
+
+ CharSequence yearString = mYearFormat.format(millis);
+ mAnimator.setContentDescription(mYearPickerDescription + ": " + yearString);
+ mAnimator.announceForAccessibility(mSelectYear);
+ break;
+ }
+ }
+
+ @Override
+ public void init(int year, int monthOfYear, int dayOfMonth,
+ DatePicker.OnDateChangedListener callBack) {
+ mDateChangedListener = callBack;
+ mCurrentDate.set(Calendar.YEAR, year);
+ mCurrentDate.set(Calendar.MONTH, monthOfYear);
+ mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+ updateDisplay(false);
+ }
+
+ @Override
+ public void updateDate(int year, int month, int dayOfMonth) {
+ mCurrentDate.set(Calendar.YEAR, year);
+ mCurrentDate.set(Calendar.MONTH, month);
+ mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+ mDateChangedListener.onDateChanged(mDelegator, year, month, dayOfMonth);
+ updateDisplay(false);
+ }
+
+ @Override
+ public int getYear() {
+ return mCurrentDate.get(Calendar.YEAR);
+ }
+
+ @Override
+ public int getMonth() {
+ return mCurrentDate.get(Calendar.MONTH);
+ }
+
+ @Override
+ public int getDayOfMonth() {
+ return mCurrentDate.get(Calendar.DAY_OF_MONTH);
+ }
+
+ @Override
+ public void setMinDate(long minDate) {
+ mTempDate.setTimeInMillis(minDate);
+ if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
+ && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
+ return;
+ }
+ if (mCurrentDate.before(mTempDate)) {
+ mCurrentDate.setTimeInMillis(minDate);
+ updatePickers();
+ updateDisplay(false);
+ }
+ mMinDate.setTimeInMillis(minDate);
+ mDayPickerView.goTo(getSelectedDay(), false, true, true);
+ }
+
+ @Override
+ public Calendar getMinDate() {
+ return mMinDate;
+ }
+
+ @Override
+ public void setMaxDate(long maxDate) {
+ mTempDate.setTimeInMillis(maxDate);
+ if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
+ && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
+ return;
+ }
+ if (mCurrentDate.after(mTempDate)) {
+ mCurrentDate.setTimeInMillis(maxDate);
+ updatePickers();
+ updateDisplay(false);
+ }
+ mMaxDate.setTimeInMillis(maxDate);
+ mDayPickerView.goTo(getSelectedDay(), false, true, true);
+ }
+
+ @Override
+ public Calendar getMaxDate() {
+ return mMaxDate;
+ }
+
+ @Override
+ public int getFirstDayOfWeek() {
+ return mCurrentDate.getFirstDayOfWeek();
+ }
+
+ @Override
+ public int getMinYear() {
+ return mMinDate.get(Calendar.YEAR);
+ }
+
+ @Override
+ public int getMaxYear() {
+ return mMaxDate.get(Calendar.YEAR);
+ }
+
+ @Override
+ public int getMinMonth() {
+ return mMinDate.get(Calendar.MONTH);
+ }
+
+ @Override
+ public int getMaxMonth() {
+ return mMaxDate.get(Calendar.MONTH);
+ }
+
+ @Override
+ public int getMinDay() {
+ return mMinDate.get(Calendar.DAY_OF_MONTH);
+ }
+
+ @Override
+ public int getMaxDay() {
+ return mMaxDate.get(Calendar.DAY_OF_MONTH);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mMonthAndDayLayout.setEnabled(enabled);
+ mSelectedYearView.setEnabled(enabled);
+ mAnimator.setEnabled(enabled);
+ mIsEnabled = enabled;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ @Override
+ public void setDateSelectorDayOfWeekBackgroundColor(int color) {
+ if (mDayOfWeekBackgroundColor != color) {
+ mDayOfWeekBackgroundColor = color;
+ mDayOfWeekView.setBackgroundColor(color);
+ }
+ }
+
+ @Override
+ public int getDateSelectorDayOfWeekBackgroundColor() {
+ return mDayOfWeekBackgroundColor;
+ }
+
+ @Override
+ public void setDateSelectorDayOfWeekTextAppearance(int resId) {
+ if (mDayOfWeekTextAppearanceResId != resId && resId > 0) {
+ mDayOfWeekTextAppearanceResId = resId;
+ mDayOfWeekView.setTextAppearance(mContext, resId);
+ }
+ }
+
+ @Override
+ public int getDateSelectorDayOfWeekTextAppearance() {
+ return mDayOfWeekTextAppearanceResId;
+ }
+
+ @Override
+ public void setDateSelectorBackgroundColor(int color) {
+ if (mMonthAndDayBackgroundColor != color) {
+ mMonthAndDayBackgroundColor = color;
+ mMonthAndDayLayout.setBackgroundColor(color);
+ }
+ }
+
+ @Override
+ public int getDateSelectorBackgroundColor() {
+ return mMonthAndDayBackgroundColor;
+ }
+
+ @Override
+ public void setDateSelectorMonthTextAppearance(int resId) {
+ if (mMonthTextAppearanceResId != resId && resId > 0) {
+ mMonthTextAppearanceResId = resId;
+ mSelectedMonthTextView.setTextAppearance(mContext, resId);
+ }
+ }
+
+ @Override
+ public int getDateSelectorMonthTextAppearance() {
+ return mMonthTextAppearanceResId;
+ }
+
+ @Override
+ public void setDateSelectorDayOfMonthTextAppearance(int resId) {
+ if (mDayOfMonthTextAppearanceResId != resId && resId > 0) {
+ mDayOfMonthTextAppearanceResId = resId;
+ mSelectedDayTextView.setTextAppearance(mContext, resId);
+ }
+ }
+
+ @Override
+ public int getDateSelectorDayOfMonthTextAppearance() {
+ return mDayOfMonthTextAppearanceResId;
+ }
+
+ @Override
+ public void setDateSelectorYearTextAppearance(int resId) {
+ if (mYearTextAppearanceResId != resId && resId > 0) {
+ mYearTextAppearanceResId = resId;
+ mSelectedYearView.setTextAppearance(mContext, resId);
+ }
+ }
+
+ @Override
+ public int getDateSelectorYearTextAppearance() {
+ return mYearTextAppearanceResId;
+ }
+
+ @Override
+ public void setDateSelectorYearListItemTextAppearance(int resId) {
+ if (mYearListItemTextAppearanceResId != resId) {
+ mYearListItemTextAppearanceResId = resId;
+ mYearPickerView.setItemTextAppearance(resId);
+ }
+ }
+
+ @Override
+ public int getDateSelectorYearListItemTextAppearance() {
+ return mYearListItemTextAppearanceResId;
+ }
+
+ @Override
+ public void setDateSelectorYearListSelectedCircleColor(int color) {
+ mYearPickerView.setYearSelectedCircleColor(color);
+ }
+
+ @Override
+ public int getDateSelectorYearListSelectedCircleColor() {
+ return mYearPickerView.getYearSelectedCircleColor();
+ }
+
+ @Override
+ public void setCalendarTextColor(ColorStateList colors) {
+ if (colors == null) {
+ return;
+ }
+ if (mCalendarTextColors == null || !mCalendarTextColors.equals(colors)) {
+ mCalendarTextColors = colors;
+ mDayPickerView.setCalendarTextColor(colors);
+ }
+ }
+
+ @Override
+ public ColorStateList getCalendarTextColors() {
+ return mCalendarTextColors;
+ }
+
+ @Override
+ public CalendarView getCalendarView() {
+ throw new UnsupportedOperationException(
+ "CalendarView does not exists for the new DatePicker");
+ }
+
+ @Override
+ public void setCalendarViewShown(boolean shown) {
+ // No-op for compatibility with the old DatePicker.
+ }
+
+ @Override
+ public boolean getCalendarViewShown() {
+ return false;
+ }
+
+ @Override
+ public void setSpinnersShown(boolean shown) {
+ // No-op for compatibility with the old DatePicker.
+ }
+
+ @Override
+ public boolean getSpinnersShown() {
+ return false;
+ }
+
+ @Override
+ public void setShowDoneButton(boolean showDoneButton) {
+ mShowDoneButton = showDoneButton;
+ updateDoneButtonVisibility();
+ }
+
+ private void updateDoneButtonVisibility() {
+ mLayoutButtons.setVisibility(mShowDoneButton ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void setDismissCallback(DatePicker.DatePickerDismissCallback callback) {
+ mDismissCallback = callback;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ mYearFormat = new SimpleDateFormat("y", newConfig.locale);
+ mDayFormat = new SimpleDateFormat("d", newConfig.locale);
+ }
+
+ @Override
+ public void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ // Nothing to do
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState(Parcelable superState) {
+ final int year = mCurrentDate.get(Calendar.YEAR);
+ final int month = mCurrentDate.get(Calendar.MONTH);
+ final int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
+
+ int listPosition = -1;
+ int listPositionOffset = -1;
+
+ if (mCurrentView == MONTH_AND_DAY_VIEW) {
+ listPosition = mDayPickerView.getMostVisiblePosition();
+ } else if (mCurrentView == YEAR_VIEW) {
+ listPosition = mYearPickerView.getFirstVisiblePosition();
+ listPositionOffset = mYearPickerView.getFirstPositionOffset();
+ }
+
+ return new SavedState(superState, year, month, day, mMinDate.getTimeInMillis(),
+ mMaxDate.getTimeInMillis(), mCurrentView, listPosition, listPositionOffset);
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ mCurrentDate.set(ss.getSelectedDay(), ss.getSelectedMonth(), ss.getSelectedYear());
+ mCurrentView = ss.getCurrentView();
+ mMinDate.setTimeInMillis(ss.getMinDate());
+ mMaxDate.setTimeInMillis(ss.getMaxDate());
+
+ updateDisplay(false);
+ setCurrentView(mCurrentView);
+
+ final int listPosition = ss.getListPosition();
+ if (listPosition != -1) {
+ if (mCurrentView == MONTH_AND_DAY_VIEW) {
+ mDayPickerView.postSetSelection(listPosition);
+ } else if (mCurrentView == YEAR_VIEW) {
+ mYearPickerView.postSetSelectionFromTop(listPosition, ss.getListPositionOffset());
+ }
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ return true;
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ event.getText().add(mCurrentDate.getTime().toString());
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(DatePicker.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setClassName(DatePicker.class.getName());
+ }
+
+ @Override
+ public void onYearSelected(int year) {
+ adjustDayInMonthIfNeeded(mCurrentDate.get(Calendar.MONTH), year);
+ mCurrentDate.set(Calendar.YEAR, year);
+ updatePickers();
+ setCurrentView(MONTH_AND_DAY_VIEW);
+ updateDisplay(true);
+ updateDoneButtonEnableState();
+ }
+
+ // If the newly selected month / year does not contain the currently selected day number,
+ // change the selected day number to the last day of the selected month or year.
+ // e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
+ // e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
+ private void adjustDayInMonthIfNeeded(int month, int year) {
+ int day = mCurrentDate.get(Calendar.DAY_OF_MONTH);
+ int daysInMonth = getDaysInMonth(month, year);
+ if (day > daysInMonth) {
+ mCurrentDate.set(Calendar.DAY_OF_MONTH, daysInMonth);
+ }
+ }
+
+ public static int getDaysInMonth(int month, int year) {
+ switch (month) {
+ case Calendar.JANUARY:
+ case Calendar.MARCH:
+ case Calendar.MAY:
+ case Calendar.JULY:
+ case Calendar.AUGUST:
+ case Calendar.OCTOBER:
+ case Calendar.DECEMBER:
+ return 31;
+ case Calendar.APRIL:
+ case Calendar.JUNE:
+ case Calendar.SEPTEMBER:
+ case Calendar.NOVEMBER:
+ return 30;
+ case Calendar.FEBRUARY:
+ return (year % 4 == 0) ? 29 : 28;
+ default:
+ throw new IllegalArgumentException("Invalid Month");
+ }
+ }
+
+ @Override
+ public void onDayOfMonthSelected(int year, int month, int day) {
+ mCurrentDate.set(Calendar.YEAR, year);
+ mCurrentDate.set(Calendar.MONTH, month);
+ mCurrentDate.set(Calendar.DAY_OF_MONTH, day);
+ updatePickers();
+ updateDisplay(true);
+ updateDoneButtonEnableState();
+ }
+
+ private void updateDoneButtonEnableState() {
+ if (mShowDoneButton) {
+ final boolean enabled = mCurrentDate.equals(mMinDate) ||
+ mCurrentDate.equals(mMaxDate) ||
+ (mCurrentDate.after(mMinDate) && mCurrentDate.before(mMaxDate));
+ mDoneButton.setEnabled(enabled);
+ }
+ }
+
+ private void updatePickers() {
+ Iterator<OnDateChangedListener> iterator = mListeners.iterator();
+ while (iterator.hasNext()) {
+ iterator.next().onDateChanged();
+ }
+ }
+
+ @Override
+ public void registerOnDateChangedListener(OnDateChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public Calendar getSelectedDay() {
+ return mCurrentDate;
+ }
+
+ @Override
+ public void tryVibrate() {
+ mDelegator.performHapticFeedback(HapticFeedbackConstants.CALENDAR_DATE);
+ }
+
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ if (v.getId() == R.id.date_picker_year) {
+ setCurrentView(YEAR_VIEW);
+ } else if (v.getId() == R.id.date_picker_month_and_day_layout) {
+ setCurrentView(MONTH_AND_DAY_VIEW);
+ }
+ }
+
+ /**
+ * Class for managing state storing/restoring.
+ */
+ private static class SavedState extends View.BaseSavedState {
+
+ private final int mSelectedYear;
+ private final int mSelectedMonth;
+ private final int mSelectedDay;
+ private final long mMinDate;
+ private final long mMaxDate;
+ private final int mCurrentView;
+ private final int mListPosition;
+ private final int mListPositionOffset;
+
+ /**
+ * Constructor called from {@link DatePicker#onSaveInstanceState()}
+ */
+ private SavedState(Parcelable superState, int year, int month, int day,
+ long minDate, long maxDate, int currentView, int listPosition,
+ int listPositionOffset) {
+ super(superState);
+ mSelectedYear = year;
+ mSelectedMonth = month;
+ mSelectedDay = day;
+ mMinDate = minDate;
+ mMaxDate = maxDate;
+ mCurrentView = currentView;
+ mListPosition = listPosition;
+ mListPositionOffset = listPositionOffset;
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ mSelectedYear = in.readInt();
+ mSelectedMonth = in.readInt();
+ mSelectedDay = in.readInt();
+ mMinDate = in.readLong();
+ mMaxDate = in.readLong();
+ mCurrentView = in.readInt();
+ mListPosition = in.readInt();
+ mListPositionOffset = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mSelectedYear);
+ dest.writeInt(mSelectedMonth);
+ dest.writeInt(mSelectedDay);
+ dest.writeLong(mMinDate);
+ dest.writeLong(mMaxDate);
+ dest.writeInt(mCurrentView);
+ dest.writeInt(mListPosition);
+ dest.writeInt(mListPositionOffset);
+ }
+
+ public int getSelectedDay() {
+ return mSelectedDay;
+ }
+
+ public int getSelectedMonth() {
+ return mSelectedMonth;
+ }
+
+ public int getSelectedYear() {
+ return mSelectedYear;
+ }
+
+ public long getMinDate() {
+ return mMinDate;
+ }
+
+ public long getMaxDate() {
+ return mMaxDate;
+ }
+
+ public int getCurrentView() {
+ return mCurrentView;
+ }
+
+ public int getListPosition() {
+ return mListPosition;
+ }
+
+ public int getListPositionOffset() {
+ return mListPositionOffset;
+ }
+
+ @SuppressWarnings("all")
+ // suppress unused and hiding
+ public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
+
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ /**
+ * Render an animator to pulsate a view in place.
+ * @param labelToAnimate the view to pulsate.
+ * @return The animator object. Use .start() to begin.
+ */
+ public static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio,
+ float increaseRatio) {
+ Keyframe k0 = Keyframe.ofFloat(0f, 1f);
+ Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio);
+ Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio);
+ Keyframe k3 = Keyframe.ofFloat(1f, 1f);
+
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe(View.SCALE_X, k0, k1, k2, k3);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe(View.SCALE_Y, k0, k1, k2, k3);
+ ObjectAnimator pulseAnimator =
+ ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY);
+ pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION);
+
+ return pulseAnimator;
+ }
+}
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
new file mode 100644
index 0000000..c44bd46
--- /dev/null
+++ b/core/java/android/widget/DayPickerView.java
@@ -0,0 +1,506 @@
+/*
+ * 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 android.widget;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * This displays a list of months in a calendar format with selectable days.
+ */
+class DayPickerView extends ListView implements AbsListView.OnScrollListener,
+ OnDateChangedListener {
+
+ private static final String TAG = "DayPickerView";
+
+ // How long the GoTo fling animation should last
+ private static final int GOTO_SCROLL_DURATION = 250;
+
+ // How long to wait after receiving an onScrollStateChanged notification before acting on it
+ private static final int SCROLL_CHANGE_DELAY = 40;
+
+ private static int LIST_TOP_OFFSET = -1; // so that the top line will be under the separator
+
+ private SimpleDateFormat mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
+
+ // These affect the scroll speed and feel
+ private float mFriction = 1.0f;
+
+ // highlighted time
+ private Calendar mSelectedDay = Calendar.getInstance();
+ private SimpleMonthAdapter mAdapter;
+
+ private Calendar mTempDay = Calendar.getInstance();
+
+ // which month should be displayed/highlighted [0-11]
+ private int mCurrentMonthDisplayed;
+ // used for tracking what state listview is in
+ private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+ // used for tracking what state listview is in
+ private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ private DatePickerController mController;
+ private boolean mPerformingScroll;
+
+ private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(this);
+
+ public DayPickerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public DayPickerView(Context context, DatePickerController controller) {
+ super(context);
+ init();
+ setController(controller);
+ }
+
+ public void setController(DatePickerController controller) {
+ if (mController != null) {
+ mController.unregisterOnDateChangedListener(this);
+ }
+ mController = controller;
+ mController.registerOnDateChangedListener(this);
+ setUpAdapter();
+ setAdapter(mAdapter);
+ onDateChanged();
+ }
+
+ public void init() {
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setDrawSelectorOnTop(false);
+
+ setUpListView();
+ }
+
+ public void onChange() {
+ setUpAdapter();
+ setAdapter(mAdapter);
+ }
+
+ /**
+ * Creates a new adapter if necessary and sets up its parameters. Override
+ * this method to provide a custom adapter.
+ */
+ protected void setUpAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new SimpleMonthAdapter(getContext(), mController);
+ } else {
+ mAdapter.setSelectedDay(mSelectedDay);
+ mAdapter.notifyDataSetChanged();
+ }
+ // refresh the view with the new parameters
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /*
+ * Sets all the required fields for the list view. Override this method to
+ * set a different list view behavior.
+ */
+ protected void setUpListView() {
+ // Transparent background on scroll
+ setCacheColorHint(0);
+ // No dividers
+ setDivider(null);
+ // Items are clickable
+ setItemsCanFocus(true);
+ // The thumb gets in the way, so disable it
+ setFastScrollEnabled(false);
+ setVerticalScrollBarEnabled(false);
+ setOnScrollListener(this);
+ setFadingEdgeLength(0);
+ // Make the scrolling behavior nicer
+ setFriction(ViewConfiguration.getScrollFriction() * mFriction);
+ }
+
+ private int getDiffMonths(Calendar start, Calendar end){
+ final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
+ final int diffMonths = end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
+ return diffMonths;
+ }
+
+ private int getPositionFromDay(Calendar day) {
+ final int diffMonthMax = getDiffMonths(mController.getMinDate(), mController.getMaxDate());
+ int diffMonth = getDiffMonths(mController.getMinDate(), day);
+
+ if (diffMonth < 0 ) {
+ diffMonth = 0;
+ } else if (diffMonth > diffMonthMax) {
+ diffMonth = diffMonthMax;
+ }
+
+ return diffMonth;
+ }
+
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param day The day to move to
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location
+ * @param setSelected Whether to set the given time as selected
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible
+ * @return Whether or not the view animated to the new location
+ */
+ public boolean goTo(Calendar day, boolean animate, boolean setSelected,
+ boolean forceScroll) {
+
+ // Set the selected day
+ if (setSelected) {
+ mSelectedDay.setTimeInMillis(day.getTimeInMillis());
+ }
+
+ mTempDay.setTimeInMillis(day.getTimeInMillis());
+ final int position = getPositionFromDay(day);
+
+ View child;
+ int i = 0;
+ int top = 0;
+ // Find a child that's completely in the view
+ do {
+ child = getChildAt(i++);
+ if (child == null) {
+ break;
+ }
+ top = child.getTop();
+ } while (top < 0);
+
+ // Compute the first and last position visible
+ int selectedPosition;
+ if (child != null) {
+ selectedPosition = getPositionForView(child);
+ } else {
+ selectedPosition = 0;
+ }
+
+ if (setSelected) {
+ mAdapter.setSelectedDay(mSelectedDay);
+ }
+
+ // Check if the selected day is now outside of our visible range
+ // and if so scroll to the month that contains it
+ if (position != selectedPosition || forceScroll) {
+ setMonthDisplayed(mTempDay);
+ mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
+ if (animate) {
+ smoothScrollToPositionFromTop(
+ position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
+ return true;
+ } else {
+ postSetSelection(position);
+ }
+ } else if (setSelected) {
+ setMonthDisplayed(mSelectedDay);
+ }
+ return false;
+ }
+
+ public void postSetSelection(final int position) {
+ clearFocus();
+ post(new Runnable() {
+
+ @Override
+ public void run() {
+ setSelection(position);
+ }
+ });
+ onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
+ }
+
+ /**
+ * Updates the title and selected month if the view has moved to a new
+ * month.
+ */
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ SimpleMonthView child = (SimpleMonthView) view.getChildAt(0);
+ if (child == null) {
+ return;
+ }
+
+ mPreviousScrollState = mCurrentScrollState;
+ }
+
+ /**
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
+ */
+ protected void setMonthDisplayed(Calendar date) {
+ if (mCurrentMonthDisplayed != date.get(Calendar.MONTH)) {
+ mCurrentMonthDisplayed = date.get(Calendar.MONTH);
+ invalidateViews();
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // use a post to prevent re-entering onScrollStateChanged before it
+ // exits
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
+
+ void setCalendarTextColor(ColorStateList colors) {
+ mAdapter.setCalendarTextColor(colors);
+ }
+
+ protected class ScrollStateRunnable implements Runnable {
+ private int mNewState;
+ private View mParent;
+
+ ScrollStateRunnable(View view) {
+ mParent = view;
+ }
+
+ /**
+ * Sets up the runnable with a short delay in case the scroll state
+ * immediately changes again.
+ *
+ * @param view The list view that changed state
+ * @param scrollState The new state it changed to
+ */
+ public void doScrollStateChange(AbsListView view, int scrollState) {
+ mParent.removeCallbacks(this);
+ mNewState = scrollState;
+ mParent.postDelayed(this, SCROLL_CHANGE_DELAY);
+ }
+
+ @Override
+ public void run() {
+ mCurrentScrollState = mNewState;
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG,
+ "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
+ }
+ // Fix the position after a scroll or a fling ends
+ if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ mPreviousScrollState = mNewState;
+ int i = 0;
+ View child = getChildAt(i);
+ while (child != null && child.getBottom() <= 0) {
+ child = getChildAt(++i);
+ }
+ if (child == null) {
+ // The view is no longer visible, just return
+ return;
+ }
+ int firstPosition = getFirstVisiblePosition();
+ int lastPosition = getLastVisiblePosition();
+ boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1;
+ final int top = child.getTop();
+ final int bottom = child.getBottom();
+ final int midpoint = getHeight() / 2;
+ if (scroll && top < LIST_TOP_OFFSET) {
+ if (bottom > midpoint) {
+ smoothScrollBy(top, GOTO_SCROLL_DURATION);
+ } else {
+ smoothScrollBy(bottom, GOTO_SCROLL_DURATION);
+ }
+ }
+ } else {
+ mPreviousScrollState = mNewState;
+ }
+ }
+ }
+
+ /**
+ * Gets the position of the view that is most prominently displayed within the list view.
+ */
+ public int getMostVisiblePosition() {
+ final int firstPosition = getFirstVisiblePosition();
+ final int height = getHeight();
+
+ int maxDisplayedHeight = 0;
+ int mostVisibleIndex = 0;
+ int i=0;
+ int bottom = 0;
+ while (bottom < height) {
+ View child = getChildAt(i);
+ if (child == null) {
+ break;
+ }
+ bottom = child.getBottom();
+ int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop());
+ if (displayedHeight > maxDisplayedHeight) {
+ mostVisibleIndex = i;
+ maxDisplayedHeight = displayedHeight;
+ }
+ i++;
+ }
+ return firstPosition + mostVisibleIndex;
+ }
+
+ @Override
+ public void onDateChanged() {
+ goTo(mController.getSelectedDay(), false, true, true);
+ }
+
+ /**
+ * Attempts to return the date that has accessibility focus.
+ *
+ * @return The date that has accessibility focus, or {@code null} if no date
+ * has focus.
+ */
+ private Calendar findAccessibilityFocus() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child instanceof SimpleMonthView) {
+ final Calendar focus = ((SimpleMonthView) child).getAccessibilityFocus();
+ if (focus != null) {
+ return focus;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempts to restore accessibility focus to a given date. No-op if
+ * {@code day} is {@code null}.
+ *
+ * @param day The date that should receive accessibility focus
+ * @return {@code true} if focus was restored
+ */
+ private boolean restoreAccessibilityFocus(Calendar day) {
+ if (day == null) {
+ return false;
+ }
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child instanceof SimpleMonthView) {
+ if (((SimpleMonthView) child).restoreAccessibilityFocus(day)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void layoutChildren() {
+ final Calendar focusedDay = findAccessibilityFocus();
+ super.layoutChildren();
+ if (mPerformingScroll) {
+ mPerformingScroll = false;
+ } else {
+ restoreAccessibilityFocus(focusedDay);
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setItemCount(-1);
+ }
+
+ private String getMonthAndYearString(Calendar day) {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append(day.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()));
+ sbuf.append(" ");
+ sbuf.append(mYearFormat.format(day.getTime()));
+ return sbuf.toString();
+ }
+
+ /**
+ * Necessary for accessibility, to ensure we support "scrolling" forward and backward
+ * in the month list.
+ */
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+
+ /**
+ * When scroll forward/backward events are received, announce the newly scrolled-to month.
+ */
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
+ action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ return super.performAccessibilityAction(action, arguments);
+ }
+
+ // Figure out what month is showing.
+ int firstVisiblePosition = getFirstVisiblePosition();
+ int month = firstVisiblePosition % 12;
+ int year = firstVisiblePosition / 12 + mController.getMinYear();
+ Calendar day = Calendar.getInstance();
+ day.set(year, month, 1);
+
+ // Scroll either forward or backward one month.
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
+ day.add(Calendar.MONTH, 1);
+ if (day.get(Calendar.MONTH) == 12) {
+ day.set(Calendar.MONTH, 0);
+ day.add(Calendar.YEAR, 1);
+ }
+ } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ View firstVisibleView = getChildAt(0);
+ // If the view is fully visible, jump one month back. Otherwise, we'll just jump
+ // to the first day of first visible month.
+ if (firstVisibleView != null && firstVisibleView.getTop() >= -1) {
+ // There's an off-by-one somewhere, so the top of the first visible item will
+ // actually be -1 when it's at the exact top.
+ day.add(Calendar.MONTH, -1);
+ if (day.get(Calendar.MONTH) == -1) {
+ day.set(Calendar.MONTH, 11);
+ day.add(Calendar.YEAR, -1);
+ }
+ }
+ }
+
+ // Go to that month.
+ announceForAccessibility(getMonthAndYearString(day));
+ goTo(day, true, false, true);
+ mPerformingScroll = true;
+ return true;
+ }
+}
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 3758d86..57b8dcb 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -21,7 +21,6 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.content.Context;
import android.graphics.Canvas;
@@ -106,7 +105,6 @@
private float mPullDistance;
private final Rect mBounds = new Rect();
- private final RectF mArcRect = new RectF();
private final Paint mPaint = new Paint();
private float mRadius;
private float mBaseGlowHeight;
@@ -318,11 +316,9 @@
final int count = canvas.save();
- final float y = mBounds.height();
- final float centerY = y - mRadius;
final float centerX = mBounds.centerX();
+ final float centerY = mBounds.height() - mRadius;
- mArcRect.set(centerX - mRadius, centerY - mRadius, centerX + mRadius, centerY + mRadius);
canvas.scale(1.f, Math.min(mGlowScaleY, 1.f), centerX, 0);
final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f;
@@ -330,7 +326,7 @@
canvas.clipRect(mBounds);
canvas.translate(translateX, 0);
- canvas.drawArc(mArcRect, 45, 90, false, mPaint);
+ canvas.drawCircle(centerX, centerY, mRadius, mPaint);
canvas.restoreToCount(count);
boolean oneLastFrame = false;
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 93810b3..33cc66e 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1029,11 +1029,6 @@
}
@Override
- AbsPositionScroller createPositionScroller() {
- return new GridViewPositionScroller();
- }
-
- @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -2392,33 +2387,4 @@
column, 1, row, 1, isHeading, isSelected);
info.setCollectionItemInfo(itemInfo);
}
-
- /**
- * Sub-position scroller that understands the layout of a GridView.
- */
- class GridViewPositionScroller extends AbsSubPositionScroller {
- @Override
- public int getRowForPosition(int position) {
- return position / mNumColumns;
- }
-
- @Override
- public int getFirstPositionForRow(int row) {
- return row * mNumColumns;
- }
-
- @Override
- public int getHeightForRow(int row) {
- final int firstRowPosition = row * mNumColumns;
- final int lastRowPosition = Math.min(getCount(), firstRowPosition + mNumColumns);
- int maxHeight = 0;
- for (int i = firstRowPosition; i < lastRowPosition; i++) {
- final int height = getHeightForPosition(i);
- if (height > maxHeight) {
- maxHeight = height;
- }
- }
- return maxHeight;
- }
- }
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 1baeca8..9db1e05 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -3872,11 +3872,6 @@
}
@Override
- AbsPositionScroller createPositionScroller() {
- return new ListViewPositionScroller();
- }
-
- @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(ListView.class.getName());
@@ -3905,24 +3900,4 @@
0, 1, position, 1, isHeading, isSelected);
info.setCollectionItemInfo(itemInfo);
}
-
- /**
- * Sub-position scroller that understands the layout of a ListView.
- */
- class ListViewPositionScroller extends AbsSubPositionScroller {
- @Override
- public int getRowForPosition(int position) {
- return position;
- }
-
- @Override
- public int getFirstPositionForRow(int row) {
- return row;
- }
-
- @Override
- public int getHeightForRow(int row) {
- return getHeightForPosition(row);
- }
- }
}
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl b/core/java/android/widget/OnDateChangedListener.java
similarity index 65%
rename from telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl
rename to core/java/android/widget/OnDateChangedListener.java
index 3a02b06..29be888 100644
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl
+++ b/core/java/android/widget/OnDateChangedListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.telephony;
+package android.widget;
/**
- * Callback interface for when DTMF has been sent.
+ * The callback used to notify other date picker components of a change in the selected date.
+ *
*/
-oneway interface IThirdPartyCallSendDtmfCallback {
- /**
- * Called when the DTMF code has been sent.
- */
- void onSendDtmfCompleted();
+interface OnDateChangedListener {
+
+ public void onDateChanged();
}
+
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index a35d447..41d3e320 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1096,10 +1096,6 @@
p.softInputMode = mSoftInputMode;
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
- // TODO: Use real shadow insets once that algorithm is finalized.
- final int shadowInset = (int) Math.ceil(mElevation * 2);
- p.shadowInsets.set(shadowInset, shadowInset, shadowInset, shadowInset);
-
return p;
}
diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java
new file mode 100644
index 0000000..53d0839
--- /dev/null
+++ b/core/java/android/widget/SimpleMonthAdapter.java
@@ -0,0 +1,178 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Calendar;
+import java.util.HashMap;
+
+/**
+ * An adapter for a list of {@link android.widget.SimpleMonthView} items.
+ */
+class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayClickListener {
+ private static final String TAG = "SimpleMonthAdapter";
+
+ private final Context mContext;
+ private final DatePickerController mController;
+ private Calendar mSelectedDay;
+
+ private ColorStateList mCalendarTextColors;
+
+ public SimpleMonthAdapter(Context context, DatePickerController controller) {
+ mContext = context;
+ mController = controller;
+ init();
+ setSelectedDay(mController.getSelectedDay());
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param day The day to highlight
+ */
+ public void setSelectedDay(Calendar day) {
+ if (mSelectedDay != day) {
+ mSelectedDay = day;
+ notifyDataSetChanged();
+ }
+ }
+
+ void setCalendarTextColor(ColorStateList colors) {
+ mCalendarTextColors = colors;
+ }
+
+ /**
+ * Set up the gesture detector and selected time
+ */
+ protected void init() {
+ mSelectedDay = Calendar.getInstance();
+ }
+
+ @Override
+ public int getCount() {
+ final int diffYear = mController.getMaxYear() - mController.getMinYear();
+ final int diffMonth = 1 + mController.getMaxMonth() - mController.getMinMonth()
+ + 12 * diffYear;
+ return diffMonth;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ SimpleMonthView v;
+ HashMap<String, Integer> drawingParams = null;
+ if (convertView != null) {
+ v = (SimpleMonthView) convertView;
+ // We store the drawing parameters in the view so it can be recycled
+ drawingParams = (HashMap<String, Integer>) v.getTag();
+ } else {
+ v = new SimpleMonthView(mContext);
+ // Set up the new view
+ AbsListView.LayoutParams params = new AbsListView.LayoutParams(
+ AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.MATCH_PARENT);
+ v.setLayoutParams(params);
+ v.setClickable(true);
+ v.setOnDayClickListener(this);
+ if (mCalendarTextColors != null) {
+ v.setTextColor(mCalendarTextColors);
+ }
+ }
+ if (drawingParams == null) {
+ drawingParams = new HashMap<String, Integer>();
+ } else {
+ drawingParams.clear();
+ }
+ final int currentMonth = position + mController.getMinMonth();
+ final int month = currentMonth % 12;
+ final int year = currentMonth / 12 + mController.getMinYear();
+
+ int selectedDay = -1;
+ if (isSelectedDayInMonth(year, month)) {
+ selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH);
+ }
+
+ // Invokes requestLayout() to ensure that the recycled view is set with the appropriate
+ // height/number of weeks before being displayed.
+ v.reuse();
+
+ final int enabledDayRangeStart;
+ if (mController.getMinMonth() == month && mController.getMinYear() == year) {
+ enabledDayRangeStart = mController.getMinDay();
+ } else {
+ enabledDayRangeStart = 1;
+ }
+
+ final int enabledDayRangeEnd;
+ if (mController.getMaxMonth() == month && mController.getMaxYear() == year) {
+ enabledDayRangeEnd = mController.getMaxDay();
+ } else {
+ enabledDayRangeEnd = 31;
+ }
+
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_YEAR, year);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_MONTH, month);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_WEEK_START, mController.getFirstDayOfWeek());
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_ENABLEDDAYRANGE_START, enabledDayRangeStart);
+ drawingParams.put(SimpleMonthView.VIEW_PARAMS_ENABLEDDAYRANGE_END, enabledDayRangeEnd);
+ v.setMonthParams(drawingParams);
+ v.invalidate();
+ return v;
+ }
+
+ private boolean isSelectedDayInMonth(int year, int month) {
+ return mSelectedDay.get(Calendar.YEAR) == year && mSelectedDay.get(Calendar.MONTH) == month;
+ }
+
+ @Override
+ public void onDayClick(SimpleMonthView view, Calendar day) {
+ if (day != null) {
+ onDayTapped(day);
+ }
+ }
+
+ /**
+ * Maintains the same hour/min/sec but moves the day to the tapped day.
+ *
+ * @param day The day that was tapped
+ */
+ protected void onDayTapped(Calendar day) {
+ mController.tryVibrate();
+ mController.onDayOfMonthSelected(day.get(Calendar.YEAR), day.get(Calendar.MONTH),
+ day.get(Calendar.DAY_OF_MONTH));
+ setSelectedDay(day);
+ }
+}
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
new file mode 100644
index 0000000..7589711
--- /dev/null
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -0,0 +1,720 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.R;
+import com.android.internal.widget.ExploreByTouchHelper;
+
+import java.security.InvalidParameterException;
+import java.util.Calendar;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A calendar-like view displaying a specified month and the appropriate selectable day numbers
+ * within the specified month.
+ */
+class SimpleMonthView extends View {
+ private static final String TAG = "SimpleMonthView";
+
+ /**
+ * These params can be passed into the view to control how it appears.
+ * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
+ * values are unlikely to fit most layouts correctly.
+ */
+ /**
+ * This sets the height of this week in pixels
+ */
+ static final String VIEW_PARAMS_HEIGHT = "height";
+ /**
+ * This specifies the position (or weeks since the epoch) of this week,
+ * calculated using
+ */
+ static final String VIEW_PARAMS_MONTH = "month";
+ /**
+ * This specifies the position (or weeks since the epoch) of this week,
+ * calculated using
+ */
+ static final String VIEW_PARAMS_YEAR = "year";
+ /**
+ * This sets one of the days in this view as selected {@link Time#SUNDAY}
+ * through {@link Time#SATURDAY}.
+ */
+ static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
+ /**
+ * Which day the week should start on. {@link Time#SUNDAY} through
+ * {@link Time#SATURDAY}.
+ */
+ static final String VIEW_PARAMS_WEEK_START = "week_start";
+ /**
+ * First enabled day.
+ */
+ static final String VIEW_PARAMS_ENABLEDDAYRANGE_START = "enabled_day_range_start";
+ /**
+ * Last enabled day.
+ */
+ static final String VIEW_PARAMS_ENABLEDDAYRANGE_END = "enabled_day_range_end";
+
+ private static int DEFAULT_HEIGHT = 32;
+ private static int MIN_HEIGHT = 10;
+
+ private static final int DEFAULT_SELECTED_DAY = -1;
+ private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
+ private static final int DEFAULT_NUM_DAYS = 7;
+ private static final int DEFAULT_NUM_ROWS = 6;
+ private static final int MAX_NUM_ROWS = 6;
+
+ private static final int SELECTED_CIRCLE_ALPHA = 60;
+
+ private static int DAY_SEPARATOR_WIDTH = 1;
+
+ private int mMiniDayNumberTextSize;
+ private int mMonthLabelTextSize;
+ private int mMonthDayLabelTextSize;
+ private int mMonthHeaderSize;
+ private int mDaySelectedCircleSize;
+
+ // used for scaling to the device density
+ private static float mScale = 0;
+
+ // affects the padding on the sides of this view
+ private int mPadding = 0;
+
+ private String mDayOfWeekTypeface;
+ private String mMonthTitleTypeface;
+
+ private Paint mDayNumberPaint;
+ private Paint mDayNumberDisabledPaint;
+ private Paint mDayNumberSelectedPaint;
+
+ private Paint mMonthTitlePaint;
+ private Paint mMonthDayLabelPaint;
+
+ private final Formatter mFormatter;
+ private final StringBuilder mStringBuilder;
+
+ private int mMonth;
+ private int mYear;
+
+ // Quick reference to the width of this view, matches parent
+ private int mWidth;
+
+ // The height this view should draw at in pixels, set by height param
+ private int mRowHeight = DEFAULT_HEIGHT;
+
+ // If this view contains the today
+ private boolean mHasToday = false;
+
+ // Which day is selected [0-6] or -1 if no day is selected
+ private int mSelectedDay = -1;
+
+ // Which day is today [0-6] or -1 if no day is today
+ private int mToday = DEFAULT_SELECTED_DAY;
+
+ // Which day of the week to start on [0-6]
+ private int mWeekStart = DEFAULT_WEEK_START;
+
+ // How many days to display
+ private int mNumDays = DEFAULT_NUM_DAYS;
+
+ // The number of days + a spot for week number if it is displayed
+ private int mNumCells = mNumDays;
+
+ private int mDayOfWeekStart = 0;
+
+ // First enabled day
+ private int mEnabledDayStart = 1;
+
+ // Last enabled day
+ private int mEnabledDayEnd = 31;
+
+ private final Calendar mCalendar = Calendar.getInstance();
+ private final Calendar mDayLabelCalendar = Calendar.getInstance();
+
+ private final MonthViewTouchHelper mTouchHelper;
+
+ private int mNumRows = DEFAULT_NUM_ROWS;
+
+ // Optional listener for handling day click actions
+ private OnDayClickListener mOnDayClickListener;
+
+ // Whether to prevent setting the accessibility delegate
+ private boolean mLockAccessibilityDelegate;
+
+ private int mNormalTextColor;
+ private int mDisabledTextColor;
+ private int mSelectedDayColor;
+
+ public SimpleMonthView(Context context) {
+ this(context, null);
+ }
+
+ public SimpleMonthView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.datePickerStyle);
+ }
+
+ public SimpleMonthView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs);
+
+ final Resources res = context.getResources();
+
+ mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
+ mMonthTitleTypeface = res.getString(R.string.sans_serif);
+
+ mStringBuilder = new StringBuilder(50);
+ mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
+
+ mMiniDayNumberTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size);
+ mMonthLabelTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size);
+ mMonthDayLabelTextSize = res.getDimensionPixelSize(
+ R.dimen.datepicker_month_day_label_text_size);
+ mMonthHeaderSize = res.getDimensionPixelOffset(
+ R.dimen.datepicker_month_list_item_header_height);
+ mDaySelectedCircleSize = res.getDimensionPixelSize(
+ R.dimen.datepicker_day_number_select_circle_radius);
+
+ mRowHeight = (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height)
+ - mMonthHeaderSize) / MAX_NUM_ROWS;
+
+ // Set up accessibility components.
+ mTouchHelper = new MonthViewTouchHelper(this);
+ setAccessibilityDelegate(mTouchHelper);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mLockAccessibilityDelegate = true;
+
+ // Sets up any standard paints that will be used
+ initView();
+ }
+
+ void setTextColor(ColorStateList colors) {
+ final Resources res = getContext().getResources();
+
+ mNormalTextColor = colors.getColorForState(ENABLED_STATE_SET,
+ res.getColor(R.color.datepicker_default_normal_text_color_holo_light));
+ mMonthTitlePaint.setColor(mNormalTextColor);
+ mMonthDayLabelPaint.setColor(mNormalTextColor);
+
+ mDisabledTextColor = colors.getColorForState(EMPTY_STATE_SET,
+ res.getColor(R.color.datepicker_default_disabled_text_color_holo_light));
+ mDayNumberDisabledPaint.setColor(mDisabledTextColor);
+
+ mSelectedDayColor = colors.getColorForState(ENABLED_SELECTED_STATE_SET,
+ res.getColor(R.color.holo_blue_light));
+ mDayNumberSelectedPaint.setColor(mSelectedDayColor);
+ mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+ }
+
+ @Override
+ public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+ // Workaround for a JB MR1 issue where accessibility delegates on
+ // top-level ListView items are overwritten.
+ if (!mLockAccessibilityDelegate) {
+ super.setAccessibilityDelegate(delegate);
+ }
+ }
+
+ public void setOnDayClickListener(OnDayClickListener listener) {
+ mOnDayClickListener = listener;
+ }
+
+ @Override
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ // First right-of-refusal goes the touch exploration helper.
+ if (mTouchHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ final int day = getDayFromLocation(event.getX(), event.getY());
+ if (day >= 0) {
+ onDayClick(day);
+ }
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Sets up the text and style properties for painting.
+ */
+ private void initView() {
+ mMonthTitlePaint = new Paint();
+ mMonthTitlePaint.setAntiAlias(true);
+ mMonthTitlePaint.setColor(mNormalTextColor);
+ mMonthTitlePaint.setTextSize(mMonthLabelTextSize);
+ mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
+ mMonthTitlePaint.setTextAlign(Align.CENTER);
+ mMonthTitlePaint.setStyle(Style.FILL);
+ mMonthTitlePaint.setFakeBoldText(true);
+
+ mMonthDayLabelPaint = new Paint();
+ mMonthDayLabelPaint.setAntiAlias(true);
+ mMonthDayLabelPaint.setColor(mNormalTextColor);
+ mMonthDayLabelPaint.setTextSize(mMonthDayLabelTextSize);
+ mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
+ mMonthDayLabelPaint.setTextAlign(Align.CENTER);
+ mMonthDayLabelPaint.setStyle(Style.FILL);
+ mMonthDayLabelPaint.setFakeBoldText(true);
+
+ mDayNumberSelectedPaint = new Paint();
+ mDayNumberSelectedPaint.setAntiAlias(true);
+ mDayNumberSelectedPaint.setColor(mSelectedDayColor);
+ mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+ mDayNumberSelectedPaint.setTextAlign(Align.CENTER);
+ mDayNumberSelectedPaint.setStyle(Style.FILL);
+ mDayNumberSelectedPaint.setFakeBoldText(true);
+
+ mDayNumberPaint = new Paint();
+ mDayNumberPaint.setAntiAlias(true);
+ mDayNumberPaint.setTextSize(mMiniDayNumberTextSize);
+ mDayNumberPaint.setTextAlign(Align.CENTER);
+ mDayNumberPaint.setStyle(Style.FILL);
+ mDayNumberPaint.setFakeBoldText(false);
+
+ mDayNumberDisabledPaint = new Paint();
+ mDayNumberDisabledPaint.setAntiAlias(true);
+ mDayNumberDisabledPaint.setColor(mDisabledTextColor);
+ mDayNumberDisabledPaint.setTextSize(mMiniDayNumberTextSize);
+ mDayNumberDisabledPaint.setTextAlign(Align.CENTER);
+ mDayNumberDisabledPaint.setStyle(Style.FILL);
+ mDayNumberDisabledPaint.setFakeBoldText(false);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawMonthTitle(canvas);
+ drawWeekDayLabels(canvas);
+ drawDays(canvas);
+ }
+
+ /**
+ * Sets all the parameters for displaying this week. The only required
+ * parameter is the week number. Other parameters have a default value and
+ * will only update if a new value is included, except for focus month,
+ * which will always default to no focus month if no value is passed in. See
+ * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
+ *
+ * @param params A map of the new parameters, see
+ * {@link #VIEW_PARAMS_HEIGHT}
+ */
+ void setMonthParams(HashMap<String, Integer> params) {
+ if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
+ throw new InvalidParameterException(
+ "You must specify the month and year for this view");
+ }
+ setTag(params);
+ // We keep the current value for any params not present
+ if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
+ mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
+ if (mRowHeight < MIN_HEIGHT) {
+ mRowHeight = MIN_HEIGHT;
+ }
+ }
+ if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
+ mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
+ }
+
+ // Allocate space for caching the day numbers and focus values
+ mMonth = params.get(VIEW_PARAMS_MONTH);
+ mYear = params.get(VIEW_PARAMS_YEAR);
+
+ // Figure out what day today is
+ final Time today = new Time(Time.getCurrentTimezone());
+ today.setToNow();
+ mHasToday = false;
+ mToday = -1;
+
+ mCalendar.set(Calendar.MONTH, mMonth);
+ mCalendar.set(Calendar.YEAR, mYear);
+ mCalendar.set(Calendar.DAY_OF_MONTH, 1);
+ mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
+
+ if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
+ mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
+ } else {
+ mWeekStart = mCalendar.getFirstDayOfWeek();
+ }
+
+ if (params.containsKey(VIEW_PARAMS_ENABLEDDAYRANGE_START)) {
+ mEnabledDayStart = params.get(VIEW_PARAMS_ENABLEDDAYRANGE_START);
+ }
+ if (params.containsKey(VIEW_PARAMS_ENABLEDDAYRANGE_END)) {
+ mEnabledDayEnd = params.get(VIEW_PARAMS_ENABLEDDAYRANGE_END);
+ }
+
+ mNumCells = getDaysInMonth(mMonth, mYear);
+ for (int i = 0; i < mNumCells; i++) {
+ final int day = i + 1;
+ if (sameDay(day, today)) {
+ mHasToday = true;
+ mToday = day;
+ }
+ }
+ mNumRows = calculateNumRows();
+
+ // Invalidate cached accessibility information.
+ mTouchHelper.invalidateRoot();
+ }
+
+ private static int getDaysInMonth(int month, int year) {
+ switch (month) {
+ case Calendar.JANUARY:
+ case Calendar.MARCH:
+ case Calendar.MAY:
+ case Calendar.JULY:
+ case Calendar.AUGUST:
+ case Calendar.OCTOBER:
+ case Calendar.DECEMBER:
+ return 31;
+ case Calendar.APRIL:
+ case Calendar.JUNE:
+ case Calendar.SEPTEMBER:
+ case Calendar.NOVEMBER:
+ return 30;
+ case Calendar.FEBRUARY:
+ return (year % 4 == 0) ? 29 : 28;
+ default:
+ throw new IllegalArgumentException("Invalid Month");
+ }
+ }
+
+ public void reuse() {
+ mNumRows = DEFAULT_NUM_ROWS;
+ requestLayout();
+ }
+
+ private int calculateNumRows() {
+ int offset = findDayOffset();
+ int dividend = (offset + mNumCells) / mNumDays;
+ int remainder = (offset + mNumCells) % mNumDays;
+ return (dividend + (remainder > 0 ? 1 : 0));
+ }
+
+ private boolean sameDay(int day, Time today) {
+ return mYear == today.year &&
+ mMonth == today.month &&
+ day == today.monthDay;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
+ + mMonthHeaderSize);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+
+ // Invalidate cached accessibility information.
+ mTouchHelper.invalidateRoot();
+ }
+
+ private String getMonthAndYearString() {
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
+ | DateUtils.FORMAT_NO_MONTH_DAY;
+ mStringBuilder.setLength(0);
+ long millis = mCalendar.getTimeInMillis();
+ return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags,
+ Time.getCurrentTimezone()).toString();
+ }
+
+ private void drawMonthTitle(Canvas canvas) {
+ int x = (mWidth + 2 * mPadding) / 2;
+ int y = (mMonthHeaderSize - mMonthDayLabelTextSize) / 2 + (mMonthLabelTextSize / 3);
+ canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
+ }
+
+ private void drawWeekDayLabels(Canvas canvas) {
+ int y = mMonthHeaderSize - (mMonthDayLabelTextSize / 2);
+ int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+ for (int i = 0; i < mNumDays; i++) {
+ int calendarDay = (i + mWeekStart) % mNumDays;
+ int x = (2 * i + 1) * dayWidthHalf + mPadding;
+ mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
+ canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
+ Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y,
+ mMonthDayLabelPaint);
+ }
+ }
+
+ /**
+ * Draws the month days.
+ */
+ private void drawDays(Canvas canvas) {
+ int y = (((mRowHeight + mMiniDayNumberTextSize) / 2) - DAY_SEPARATOR_WIDTH)
+ + mMonthHeaderSize;
+ int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+ int j = findDayOffset();
+ for (int day = 1; day <= mNumCells; day++) {
+ int x = (2 * j + 1) * dayWidthHalf + mPadding;
+ if (mSelectedDay == day) {
+ canvas.drawCircle(x, y - (mMiniDayNumberTextSize / 3), mDaySelectedCircleSize,
+ mDayNumberSelectedPaint);
+ }
+
+ if (mHasToday && mToday == day) {
+ mDayNumberPaint.setColor(mSelectedDayColor);
+ } else {
+ mDayNumberPaint.setColor(mNormalTextColor);
+ }
+ final Paint paint = (day < mEnabledDayStart || day > mEnabledDayEnd) ?
+ mDayNumberDisabledPaint : mDayNumberPaint;
+ canvas.drawText(String.format("%d", day), x, y, paint);
+ j++;
+ if (j == mNumDays) {
+ j = 0;
+ y += mRowHeight;
+ }
+ }
+ }
+
+ private int findDayOffset() {
+ return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
+ - mWeekStart;
+ }
+
+ /**
+ * Calculates the day that the given x position is in, accounting for week
+ * number. Returns the day or -1 if the position wasn't in a day.
+ *
+ * @param x The x position of the touch event
+ * @return The day number, or -1 if the position wasn't in a day
+ */
+ private int getDayFromLocation(float x, float y) {
+ int dayStart = mPadding;
+ if (x < dayStart || x > mWidth - mPadding) {
+ return -1;
+ }
+ // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
+ int row = (int) (y - mMonthHeaderSize) / mRowHeight;
+ int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
+
+ int day = column - findDayOffset() + 1;
+ day += row * mNumDays;
+ if (day < 1 || day > mNumCells) {
+ return -1;
+ }
+ return day;
+ }
+
+ /**
+ * Called when the user clicks on a day. Handles callbacks to the
+ * {@link OnDayClickListener} if one is set.
+ *
+ * @param day The day that was clicked
+ */
+ private void onDayClick(int day) {
+ if (mOnDayClickListener != null) {
+ Calendar date = Calendar.getInstance();
+ date.set(mYear, mMonth, day);
+ mOnDayClickListener.onDayClick(this, date);
+ }
+
+ // This is a no-op if accessibility is turned off.
+ mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ }
+
+ /**
+ * @return The date that has accessibility focus, or {@code null} if no date
+ * has focus
+ */
+ Calendar getAccessibilityFocus() {
+ final int day = mTouchHelper.getFocusedVirtualView();
+ Calendar date = null;
+ if (day >= 0) {
+ date = Calendar.getInstance();
+ date.set(mYear, mMonth, day);
+ }
+ return date;
+ }
+
+ /**
+ * Clears accessibility focus within the view. No-op if the view does not
+ * contain accessibility focus.
+ */
+ public void clearAccessibilityFocus() {
+ mTouchHelper.clearFocusedVirtualView();
+ }
+
+ /**
+ * Attempts to restore accessibility focus to the specified date.
+ *
+ * @param day The date which should receive focus
+ * @return {@code false} if the date is not valid for this month view, or
+ * {@code true} if the date received focus
+ */
+ boolean restoreAccessibilityFocus(Calendar day) {
+ if ((day.get(Calendar.YEAR) != mYear) || (day.get(Calendar.MONTH) != mMonth) ||
+ (day.get(Calendar.DAY_OF_MONTH) > mNumCells)) {
+ return false;
+ }
+ mTouchHelper.setFocusedVirtualView(day.get(Calendar.DAY_OF_MONTH));
+ return true;
+ }
+
+ /**
+ * Provides a virtual view hierarchy for interfacing with an accessibility
+ * service.
+ */
+ private class MonthViewTouchHelper extends ExploreByTouchHelper {
+ private static final String DATE_FORMAT = "dd MMMM yyyy";
+
+ private final Rect mTempRect = new Rect();
+ private final Calendar mTempCalendar = Calendar.getInstance();
+
+ public MonthViewTouchHelper(View host) {
+ super(host);
+ }
+
+ public void setFocusedVirtualView(int virtualViewId) {
+ getAccessibilityNodeProvider(SimpleMonthView.this).performAction(
+ virtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
+ public void clearFocusedVirtualView() {
+ final int focusedVirtualView = getFocusedVirtualView();
+ if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
+ getAccessibilityNodeProvider(SimpleMonthView.this).performAction(
+ focusedVirtualView,
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
+ null);
+ }
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ final int day = getDayFromLocation(x, y);
+ if (day >= 0) {
+ return day;
+ }
+ return ExploreByTouchHelper.INVALID_ID;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+ for (int day = 1; day <= mNumCells; day++) {
+ virtualViewIds.add(day);
+ }
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ event.setContentDescription(getItemDescription(virtualViewId));
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
+ getItemBounds(virtualViewId, mTempRect);
+
+ node.setContentDescription(getItemDescription(virtualViewId));
+ node.setBoundsInParent(mTempRect);
+ node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+
+ if (virtualViewId == mSelectedDay) {
+ node.setSelected(true);
+ }
+
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+ Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_CLICK:
+ onDayClick(virtualViewId);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Calculates the bounding rectangle of a given time object.
+ *
+ * @param day The day to calculate bounds for
+ * @param rect The rectangle in which to store the bounds
+ */
+ private void getItemBounds(int day, Rect rect) {
+ final int offsetX = mPadding;
+ final int offsetY = mMonthHeaderSize;
+ final int cellHeight = mRowHeight;
+ final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
+ final int index = ((day - 1) + findDayOffset());
+ final int row = (index / mNumDays);
+ final int column = (index % mNumDays);
+ final int x = (offsetX + (column * cellWidth));
+ final int y = (offsetY + (row * cellHeight));
+
+ rect.set(x, y, (x + cellWidth), (y + cellHeight));
+ }
+
+ /**
+ * Generates a description for a given time object. Since this
+ * description will be spoken, the components are ordered by descending
+ * specificity as DAY MONTH YEAR.
+ *
+ * @param day The day to generate a description for
+ * @return A description of the time object
+ */
+ private CharSequence getItemDescription(int day) {
+ mTempCalendar.set(mYear, mMonth, day);
+ final CharSequence date = DateFormat.format(DATE_FORMAT,
+ mTempCalendar.getTimeInMillis());
+
+ if (day == mSelectedDay) {
+ return getContext().getString(R.string.item_is_selected, date);
+ }
+
+ return date;
+ }
+ }
+
+ /**
+ * Handles callbacks when the user clicks on a time object.
+ */
+ public interface OnDayClickListener {
+ public void onDayClick(SimpleMonthView view, Calendar day);
+ }
+}
diff --git a/core/java/android/widget/TextViewWithCircularIndicator.java b/core/java/android/widget/TextViewWithCircularIndicator.java
new file mode 100644
index 0000000..22d770c
--- /dev/null
+++ b/core/java/android/widget/TextViewWithCircularIndicator.java
@@ -0,0 +1,114 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+
+import com.android.internal.R;
+
+class TextViewWithCircularIndicator extends TextView {
+
+ private static final int SELECTED_CIRCLE_ALPHA = 60;
+
+ private final Paint mCirclePaint = new Paint();
+
+ private final String mItemIsSelectedText;
+ private int mCircleColor;
+ private boolean mDrawIndicator;
+
+ public TextViewWithCircularIndicator(Context context) {
+ this(context, null);
+ }
+
+ public TextViewWithCircularIndicator(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TextViewWithCircularIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TextViewWithCircularIndicator(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+
+ super(context, attrs);
+ Resources res = context.getResources();
+
+ // Use Theme attributes if possible
+ final TypedArray a = mContext.obtainStyledAttributes(attrs,
+ R.styleable.DatePicker, defStyleAttr, defStyleRes);
+
+ final int resId = a.getResourceId(
+ R.styleable.DatePicker_dateSelectorYearListItemTextAppearance, -1);
+ if (resId != -1) {
+ setTextAppearance(context, resId);
+ }
+
+ mItemIsSelectedText = res.getString(R.string.item_is_selected);
+
+ a.recycle();
+
+ init();
+ }
+
+ private void init() {
+ mCirclePaint.setTypeface(Typeface.create(mCirclePaint.getTypeface(), Typeface.BOLD));
+ mCirclePaint.setAntiAlias(true);
+ mCirclePaint.setTextAlign(Paint.Align.CENTER);
+ mCirclePaint.setStyle(Paint.Style.FILL);
+ }
+
+ public void setCircleColor(int color) {
+ if (color != mCircleColor) {
+ mCircleColor = color;
+ mCirclePaint.setColor(mCircleColor);
+ mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+ requestLayout();
+ }
+ }
+
+ public void setDrawIndicator(boolean drawIndicator) {
+ mDrawIndicator = drawIndicator;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mDrawIndicator) {
+ final int width = getWidth();
+ final int height = getHeight();
+ int radius = Math.min(width, height) / 2;
+ canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint);
+ }
+ }
+
+ @Override
+ public CharSequence getContentDescription() {
+ CharSequence itemText = getText();
+ if (mDrawIndicator) {
+ return String.format(mItemIsSelectedText, itemText);
+ } else {
+ return itemText;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/widget/TimePickerDelegate.java b/core/java/android/widget/TimePickerDelegate.java
index bf3971c..cd89667 100644
--- a/core/java/android/widget/TimePickerDelegate.java
+++ b/core/java/android/widget/TimePickerDelegate.java
@@ -1389,8 +1389,8 @@
final Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio);
final Keyframe k3 = Keyframe.ofFloat(1f, 1f);
- PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe("scaleX", k0, k1, k2, k3);
- PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe("scaleY", k0, k1, k2, k3);
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe(View.SCALE_X, k0, k1, k2, k3);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe(View.SCALE_Y, k0, k1, k2, k3);
ObjectAnimator pulseAnimator =
ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY);
pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION);
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
new file mode 100644
index 0000000..bac9320
--- /dev/null
+++ b/core/java/android/widget/YearPickerView.java
@@ -0,0 +1,200 @@
+/*
+ * 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 android.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.Calendar;
+
+import com.android.internal.R;
+
+/**
+ * Displays a selectable list of years.
+ */
+class YearPickerView extends ListView implements AdapterView.OnItemClickListener,
+ OnDateChangedListener {
+ private static final String TAG = "YearPickerView";
+
+ private DatePickerController mController;
+ private YearAdapter mAdapter;
+ private int mViewSize;
+ private int mChildSize;
+ private int mSelectedPosition = -1;
+ private int mYearSelectedCircleColor;
+
+ public YearPickerView(Context context) {
+ this(context, null);
+ }
+
+ public YearPickerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public YearPickerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public YearPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ ViewGroup.LayoutParams frame = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ setLayoutParams(frame);
+
+ Resources res = context.getResources();
+ mViewSize = res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height);
+ mChildSize = res.getDimensionPixelOffset(R.dimen.datepicker_year_label_height);
+
+ setVerticalFadingEdgeEnabled(true);
+ setFadingEdgeLength(mChildSize / 3);
+
+ final int paddingTop = res.getDimensionPixelSize(
+ R.dimen.datepicker_year_picker_padding_top);
+ setPadding(0, paddingTop, 0, 0);
+
+ // Use Theme attributes if possible
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.DatePicker, defStyleAttr, defStyleRes);
+
+ final int colorResId = a.getResourceId(
+ R.styleable.DatePicker_dateSelectorYearListSelectedCircleColor,
+ R.color.datepicker_default_circle_background_color_holo_light);
+ mYearSelectedCircleColor = res.getColor(colorResId);
+
+ a.recycle();
+
+ setOnItemClickListener(this);
+ setDividerHeight(0);
+ }
+
+ public void init(DatePickerController controller) {
+ mController = controller;
+ mController.registerOnDateChangedListener(this);
+
+ mAdapter = new YearAdapter(getContext(), R.layout.year_label_text_view);
+ updateAdapterData();
+ setAdapter(mAdapter);
+
+ onDateChanged();
+ }
+
+ public void setYearSelectedCircleColor(int color) {
+ if (color != mYearSelectedCircleColor) {
+ mYearSelectedCircleColor = color;
+ }
+ requestLayout();
+ }
+
+ public int getYearSelectedCircleColor() {
+ return mYearSelectedCircleColor;
+ }
+
+ private void updateAdapterData() {
+ mAdapter.clear();
+ final int maxYear = mController.getMaxYear();
+ for (int year = mController.getMinYear(); year <= maxYear; year++) {
+ mAdapter.add(year);
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mController.tryVibrate();
+ if (position != mSelectedPosition) {
+ mSelectedPosition = position;
+ mAdapter.notifyDataSetChanged();
+ }
+ mController.onYearSelected(mAdapter.getItem(position));
+ }
+
+ void setItemTextAppearance(int resId) {
+ mAdapter.setItemTextAppearance(resId);
+ }
+
+ private class YearAdapter extends ArrayAdapter<Integer> {
+ int mItemTextAppearanceResId;
+
+ public YearAdapter(Context context, int resource) {
+ super(context, resource);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextViewWithCircularIndicator v = (TextViewWithCircularIndicator)
+ super.getView(position, convertView, parent);
+ v.setTextAppearance(getContext(), mItemTextAppearanceResId);
+ v.requestLayout();
+ int year = getItem(position);
+ boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year;
+ v.setDrawIndicator(selected);
+ if (selected) {
+ v.setCircleColor(mYearSelectedCircleColor);
+ }
+ return v;
+ }
+
+ public void setItemTextAppearance(int resId) {
+ mItemTextAppearanceResId = resId;
+ }
+ }
+
+ public void postSetSelectionCentered(final int position) {
+ postSetSelectionFromTop(position, mViewSize / 2 - mChildSize / 2);
+ }
+
+ public void postSetSelectionFromTop(final int position, final int offset) {
+ post(new Runnable() {
+
+ @Override
+ public void run() {
+ setSelectionFromTop(position, offset);
+ requestLayout();
+ }
+ });
+ }
+
+ public int getFirstPositionOffset() {
+ final View firstChild = getChildAt(0);
+ if (firstChild == null) {
+ return 0;
+ }
+ return firstChild.getTop();
+ }
+
+ @Override
+ public void onDateChanged() {
+ updateAdapterData();
+ mAdapter.notifyDataSetChanged();
+ postSetSelectionCentered(
+ mController.getSelectedDay().get(Calendar.YEAR) - mController.getMinYear());
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+ event.setFromIndex(0);
+ event.setToIndex(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 98a5843..901d6e6 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -47,6 +47,10 @@
void noteProcessStart(String name, int uid);
void noteProcessState(String name, int uid, int state);
void noteProcessFinish(String name, int uid);
+ void noteSyncStart(String name, int uid);
+ void noteSyncFinish(String name, int uid);
+ void noteJobStart(String name, int uid);
+ void noteJobFinish(String name, int uid);
void noteStartWakelock(int uid, int pid, String name, String historyName,
int type, boolean unimportantForLogging);
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 8b56ceb..7e58351 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -56,7 +56,7 @@
public class LocalTransport extends BackupTransport {
private static final String TAG = "LocalTransport";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final String TRANSPORT_DIR_NAME
= "com.android.internal.backup.LocalTransport";
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6ca048e..dec94f2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -91,7 +91,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 109 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 112 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -2109,6 +2109,11 @@
return;
}
+ if (dataSize == 0) {
+ // The history is currently empty; we need it to start with a time stamp.
+ cur.currentTime = System.currentTimeMillis();
+ addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_RESET, cur);
+ }
addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.CMD_UPDATE, cur);
}
@@ -2362,6 +2367,50 @@
elapsedRealtime);
}
+ public void noteSyncStartLocked(String name, int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ getUidStatsLocked(uid).noteStartSyncLocked(name, elapsedRealtime);
+ if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_START, name, uid, 0)) {
+ return;
+ }
+ addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SYNC_START, name, uid);
+ }
+
+ public void noteSyncFinishLocked(String name, int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ getUidStatsLocked(uid).noteStopSyncLocked(name, elapsedRealtime);
+ if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_FINISH, name, uid, 0)) {
+ return;
+ }
+ addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_SYNC_FINISH, name, uid);
+ }
+
+ public void noteJobStartLocked(String name, int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ getUidStatsLocked(uid).noteStartJobLocked(name, elapsedRealtime);
+ if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_START, name, uid, 0)) {
+ return;
+ }
+ addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_START, name, uid);
+ }
+
+ public void noteJobFinishLocked(String name, int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ getUidStatsLocked(uid).noteStopJobLocked(name, elapsedRealtime);
+ if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
+ return;
+ }
+ addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_FINISH, name, uid);
+ }
+
private void requestWakelockCpuUpdate() {
if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
@@ -3833,6 +3882,16 @@
final ArrayMap<String, Wakelock> mWakelockStats = new ArrayMap<String, Wakelock>();
/**
+ * The statistics we have collected for this uid's syncs.
+ */
+ final ArrayMap<String, StopwatchTimer> mSyncStats = new ArrayMap<String, StopwatchTimer>();
+
+ /**
+ * The statistics we have collected for this uid's jobs.
+ */
+ final ArrayMap<String, StopwatchTimer> mJobStats = new ArrayMap<String, StopwatchTimer>();
+
+ /**
* The statistics we have collected for this uid's sensor activations.
*/
final SparseArray<Sensor> mSensorStats = new SparseArray<Sensor>();
@@ -3872,6 +3931,16 @@
}
@Override
+ public Map<String, ? extends BatteryStats.Timer> getSyncStats() {
+ return mSyncStats;
+ }
+
+ @Override
+ public Map<String, ? extends BatteryStats.Timer> getJobStats() {
+ return mJobStats;
+ }
+
+ @Override
public SparseArray<? extends BatteryStats.Uid.Sensor> getSensorStats() {
return mSensorStats;
}
@@ -4396,6 +4465,24 @@
active = true;
}
}
+ for (int is=mSyncStats.size()-1; is>=0; is--) {
+ StopwatchTimer timer = mSyncStats.valueAt(is);
+ if (timer.reset(false)) {
+ mSyncStats.removeAt(is);
+ timer.detach();
+ } else {
+ active = true;
+ }
+ }
+ for (int ij=mJobStats.size()-1; ij>=0; ij--) {
+ StopwatchTimer timer = mJobStats.valueAt(ij);
+ if (timer.reset(false)) {
+ mJobStats.removeAt(ij);
+ timer.detach();
+ } else {
+ active = true;
+ }
+ }
for (int ise=mSensorStats.size()-1; ise>=0; ise--) {
Sensor s = mSensorStats.valueAt(ise);
if (s.reset()) {
@@ -4497,6 +4584,22 @@
wakelock.writeToParcelLocked(out, elapsedRealtimeUs);
}
+ int NS = mSyncStats.size();
+ out.writeInt(NS);
+ for (int is=0; is<NS; is++) {
+ out.writeString(mSyncStats.keyAt(is));
+ StopwatchTimer timer = mSyncStats.valueAt(is);
+ Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
+ }
+
+ int NJ = mJobStats.size();
+ out.writeInt(NJ);
+ for (int ij=0; ij<NJ; ij++) {
+ out.writeString(mJobStats.keyAt(ij));
+ StopwatchTimer timer = mJobStats.valueAt(ij);
+ Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
+ }
+
int NSE = mSensorStats.size();
out.writeInt(NSE);
for (int ise=0; ise<NSE; ise++) {
@@ -4618,6 +4721,25 @@
mWakelockStats.put(wakelockName, wakelock);
}
+ int numSyncs = in.readInt();
+ mSyncStats.clear();
+ for (int j = 0; j < numSyncs; j++) {
+ String syncName = in.readString();
+ if (in.readInt() != 0) {
+ mSyncStats.put(syncName,
+ new StopwatchTimer(Uid.this, SYNC, null, timeBase, in));
+ }
+ }
+
+ int numJobs = in.readInt();
+ mJobStats.clear();
+ for (int j = 0; j < numJobs; j++) {
+ String jobName = in.readString();
+ if (in.readInt() != 0) {
+ mJobStats.put(jobName, new StopwatchTimer(Uid.this, JOB, null, timeBase, in));
+ }
+ }
+
int numSensors = in.readInt();
mSensorStats.clear();
for (int k = 0; k < numSensors; k++) {
@@ -5670,6 +5792,38 @@
return ss;
}
+ public StopwatchTimer getSyncTimerLocked(String name) {
+ StopwatchTimer t = mSyncStats.get(name);
+ if (t == null) {
+ final int N = mSyncStats.size();
+ if (N > MAX_WAKELOCKS_PER_UID) {
+ name = BATCHED_WAKELOCK_NAME;
+ t = mSyncStats.get(name);
+ }
+ if (t == null) {
+ t = new StopwatchTimer(Uid.this, SYNC, null, mOnBatteryTimeBase);
+ mSyncStats.put(name, t);
+ }
+ }
+ return t;
+ }
+
+ public StopwatchTimer getJobTimerLocked(String name) {
+ StopwatchTimer t = mJobStats.get(name);
+ if (t == null) {
+ final int N = mJobStats.size();
+ if (N > MAX_WAKELOCKS_PER_UID) {
+ name = BATCHED_WAKELOCK_NAME;
+ t = mJobStats.get(name);
+ }
+ if (t == null) {
+ t = new StopwatchTimer(Uid.this, JOB, null, mOnBatteryTimeBase);
+ mJobStats.put(name, t);
+ }
+ }
+ return t;
+ }
+
public StopwatchTimer getWakeTimerLocked(String name, int type) {
Wakelock wl = mWakelockStats.get(name);
if (wl == null) {
@@ -5737,6 +5891,34 @@
return t;
}
+ public void noteStartSyncLocked(String name, long elapsedRealtimeMs) {
+ StopwatchTimer t = getSyncTimerLocked(name);
+ if (t != null) {
+ t.startRunningLocked(elapsedRealtimeMs);
+ }
+ }
+
+ public void noteStopSyncLocked(String name, long elapsedRealtimeMs) {
+ StopwatchTimer t = getSyncTimerLocked(name);
+ if (t != null) {
+ t.stopRunningLocked(elapsedRealtimeMs);
+ }
+ }
+
+ public void noteStartJobLocked(String name, long elapsedRealtimeMs) {
+ StopwatchTimer t = getJobTimerLocked(name);
+ if (t != null) {
+ t.startRunningLocked(elapsedRealtimeMs);
+ }
+ }
+
+ public void noteStopJobLocked(String name, long elapsedRealtimeMs) {
+ StopwatchTimer t = getJobTimerLocked(name);
+ if (t != null) {
+ t.stopRunningLocked(elapsedRealtimeMs);
+ }
+ }
+
public void noteStartWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
StopwatchTimer t = getWakeTimerLocked(name, type);
if (t != null) {
@@ -7170,7 +7352,7 @@
// the last run until samples in this run.
if (mHistoryBaseTime > 0) {
long oldnow = SystemClock.elapsedRealtime();
- mHistoryBaseTime = (mHistoryBaseTime - oldnow) + 60*1000;
+ mHistoryBaseTime = mHistoryBaseTime - oldnow + 1;
if (DEBUG_HISTORY) {
StringBuilder sb = new StringBuilder(128);
sb.append("****************** ADJUSTED mHistoryBaseTime: ");
@@ -7433,6 +7615,26 @@
}
}
+ int NS = in.readInt();
+ if (NS > 100) {
+ Slog.w(TAG, "File corrupt: too many syncs " + NS);
+ return;
+ }
+ for (int is = 0; is < NS; is++) {
+ String name = in.readString();
+ u.getSyncTimerLocked(name).readSummaryFromParcelLocked(in);
+ }
+
+ int NJ = in.readInt();
+ if (NJ > 100) {
+ Slog.w(TAG, "File corrupt: too many job timers " + NJ);
+ return;
+ }
+ for (int ij = 0; ij < NJ; ij++) {
+ String name = in.readString();
+ u.getJobTimerLocked(name).readSummaryFromParcelLocked(in);
+ }
+
int NP = in.readInt();
if (NP > 1000) {
Slog.w(TAG, "File corrupt: too many sensors " + NP);
@@ -7484,7 +7686,7 @@
String pkgName = in.readString();
Uid.Pkg p = u.getPackageStatsLocked(pkgName);
p.mWakeups = p.mLoadedWakeups = in.readInt();
- final int NS = in.readInt();
+ NS = in.readInt();
if (NS > 1000) {
Slog.w(TAG, "File corrupt: too many services " + NS);
return;
@@ -7717,6 +7919,20 @@
}
}
+ int NS = u.mSyncStats.size();
+ out.writeInt(NS);
+ for (int is=0; is<NS; is++) {
+ out.writeString(u.mSyncStats.keyAt(is));
+ u.mSyncStats.valueAt(is).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ }
+
+ int NJ = u.mJobStats.size();
+ out.writeInt(NJ);
+ for (int ij=0; ij<NJ; ij++) {
+ out.writeString(u.mJobStats.keyAt(ij));
+ u.mJobStats.valueAt(ij).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ }
+
int NSE = u.mSensorStats.size();
out.writeInt(NSE);
for (int ise=0; ise<NSE; ise++) {
@@ -7760,7 +7976,7 @@
out.writeString(ent.getKey());
Uid.Pkg ps = ent.getValue();
out.writeInt(ps.mWakeups);
- final int NS = ps.mServiceStats.size();
+ NS = ps.mServiceStats.size();
out.writeInt(NS);
if (NS > 0) {
for (Map.Entry<String, BatteryStatsImpl.Uid.Pkg.Serv> sent
@@ -7786,7 +8002,7 @@
void readFromParcelLocked(Parcel in) {
int magic = in.readInt();
if (magic != MAGIC) {
- throw new ParcelFormatException("Bad magic number");
+ throw new ParcelFormatException("Bad magic number: #" + Integer.toHexString(magic));
}
readHistory(in, false);
diff --git a/core/java/com/android/internal/widget/AccessibleDateAnimator.java b/core/java/com/android/internal/widget/AccessibleDateAnimator.java
new file mode 100644
index 0000000..e91a55c
--- /dev/null
+++ b/core/java/com/android/internal/widget/AccessibleDateAnimator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ViewAnimator;
+
+/**
+ * @hide
+ */
+public class AccessibleDateAnimator extends ViewAnimator {
+ private long mDateMillis;
+
+ public AccessibleDateAnimator(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setDateMillis(long dateMillis) {
+ mDateMillis = dateMillis;
+ }
+
+ /**
+ * Announce the currently-selected date when launched.
+ */
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ // Clear the event's current text so that only the current date will be spoken.
+ event.getText().clear();
+ int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
+ DateUtils.FORMAT_SHOW_WEEKDAY;
+
+ String dateString = DateUtils.formatDateTime(getContext(), mDateMillis, flags);
+ event.getText().add(dateString);
+ return true;
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+}
diff --git a/core/java/com/android/internal/widget/ExploreByTouchHelper.java b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
new file mode 100644
index 0000000..11c4ca1
--- /dev/null
+++ b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.accessibility.*;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * ExploreByTouchHelper is a utility class for implementing accessibility
+ * support in custom {@link android.view.View}s that represent a collection of View-like
+ * logical items. It extends {@link android.view.accessibility.AccessibilityNodeProvider} and
+ * simplifies many aspects of providing information to accessibility services
+ * and managing accessibility focus. This class does not currently support
+ * hierarchies of logical items.
+ * <p>
+ * This should be applied to the parent view using
+ * {@link android.view.View#setAccessibilityDelegate}:
+ *
+ * <pre>
+ * mAccessHelper = ExploreByTouchHelper.create(someView, mAccessHelperCallback);
+ * ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);
+ * </pre>
+ */
+public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
+ /** Virtual node identifier value for invalid nodes. */
+ public static final int INVALID_ID = Integer.MIN_VALUE;
+
+ /** Default class name used for virtual views. */
+ private static final String DEFAULT_CLASS_NAME = View.class.getName();
+
+ // Temporary, reusable data structures.
+ private final Rect mTempScreenRect = new Rect();
+ private final Rect mTempParentRect = new Rect();
+ private final Rect mTempVisibleRect = new Rect();
+ private final int[] mTempGlobalRect = new int[2];
+
+ /** View's context **/
+ private Context mContext;
+
+ /** System accessibility manager, used to check state and send events. */
+ private final AccessibilityManager mManager;
+
+ /** View whose internal structure is exposed through this helper. */
+ private final View mView;
+
+ /** Node provider that handles creating nodes and performing actions. */
+ private ExploreByTouchNodeProvider mNodeProvider;
+
+ /** Virtual view id for the currently focused logical item. */
+ private int mFocusedVirtualViewId = INVALID_ID;
+
+ /** Virtual view id for the currently hovered logical item. */
+ private int mHoveredVirtualViewId = INVALID_ID;
+
+ /**
+ * Factory method to create a new {@link ExploreByTouchHelper}.
+ *
+ * @param forView View whose logical children are exposed by this helper.
+ */
+ public ExploreByTouchHelper(View forView) {
+ if (forView == null) {
+ throw new IllegalArgumentException("View may not be null");
+ }
+
+ mView = forView;
+ mContext = forView.getContext();
+ mManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ }
+
+ /**
+ * Returns the {@link android.view.accessibility.AccessibilityNodeProvider} for this helper.
+ *
+ * @param host View whose logical children are exposed by this helper.
+ * @return The accessibility node provider for this helper.
+ */
+ @Override
+ public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+ if (mNodeProvider == null) {
+ mNodeProvider = new ExploreByTouchNodeProvider();
+ }
+ return mNodeProvider;
+ }
+
+ /**
+ * Dispatches hover {@link android.view.MotionEvent}s to the virtual view hierarchy when
+ * the Explore by Touch feature is enabled.
+ * <p>
+ * This method should be called by overriding
+ * {@link View#dispatchHoverEvent}:
+ *
+ * <pre>@Override
+ * public boolean dispatchHoverEvent(MotionEvent event) {
+ * if (mHelper.dispatchHoverEvent(this, event) {
+ * return true;
+ * }
+ * return super.dispatchHoverEvent(event);
+ * }
+ * </pre>
+ *
+ * @param event The hover event to dispatch to the virtual view hierarchy.
+ * @return Whether the hover event was handled.
+ */
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ if (!mManager.isEnabled() || !mManager.isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_HOVER_ENTER:
+ final int virtualViewId = getVirtualViewAt(event.getX(), event.getY());
+ updateHoveredVirtualView(virtualViewId);
+ return (virtualViewId != INVALID_ID);
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (mFocusedVirtualViewId != INVALID_ID) {
+ updateHoveredVirtualView(INVALID_ID);
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Populates an event of the specified type with information about an item
+ * and attempts to send it up through the view hierarchy.
+ * <p>
+ * You should call this method after performing a user action that normally
+ * fires an accessibility event, such as clicking on an item.
+ *
+ * <pre>public void performItemClick(T item) {
+ * ...
+ * sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ * }
+ * </pre>
+ *
+ * @param virtualViewId The virtual view id for which to send an event.
+ * @param eventType The type of event to send.
+ * @return true if the event was sent successfully.
+ */
+ public boolean sendEventForVirtualView(int virtualViewId, int eventType) {
+ if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
+ return false;
+ }
+
+ final ViewParent parent = mView.getParent();
+ if (parent == null) {
+ return false;
+ }
+
+ final AccessibilityEvent event = createEvent(virtualViewId, eventType);
+ return parent.requestSendAccessibilityEvent(mView, event);
+ }
+
+ /**
+ * Notifies the accessibility framework that the properties of the parent
+ * view have changed.
+ * <p>
+ * You <b>must</b> call this method after adding or removing items from the
+ * parent view.
+ */
+ public void invalidateRoot() {
+ invalidateVirtualView(View.NO_ID);
+ }
+
+ /**
+ * Notifies the accessibility framework that the properties of a particular
+ * item have changed.
+ * <p>
+ * You <b>must</b> call this method after changing any of the properties set
+ * in {@link #onPopulateNodeForVirtualView}.
+ *
+ * @param virtualViewId The virtual view id to invalidate.
+ */
+ public void invalidateVirtualView(int virtualViewId) {
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ }
+
+ /**
+ * Returns the virtual view id for the currently focused item,
+ *
+ * @return A virtual view id, or {@link #INVALID_ID} if no item is
+ * currently focused.
+ */
+ public int getFocusedVirtualView() {
+ return mFocusedVirtualViewId;
+ }
+
+ /**
+ * Sets the currently hovered item, sending hover accessibility events as
+ * necessary to maintain the correct state.
+ *
+ * @param virtualViewId The virtual view id for the item currently being
+ * hovered, or {@link #INVALID_ID} if no item is hovered within
+ * the parent view.
+ */
+ private void updateHoveredVirtualView(int virtualViewId) {
+ if (mHoveredVirtualViewId == virtualViewId) {
+ return;
+ }
+
+ final int previousVirtualViewId = mHoveredVirtualViewId;
+ mHoveredVirtualViewId = virtualViewId;
+
+ // Stay consistent with framework behavior by sending ENTER/EXIT pairs
+ // in reverse order. This is accurate as of API 18.
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ sendEventForVirtualView(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ }
+
+ /**
+ * Constructs and returns an {@link AccessibilityEvent} for the specified
+ * virtual view id, which includes the host view ({@link View#NO_ID}).
+ *
+ * @param virtualViewId The virtual view id for the item for which to
+ * construct an event.
+ * @param eventType The type of event to construct.
+ * @return An {@link AccessibilityEvent} populated with information about
+ * the specified item.
+ */
+ private AccessibilityEvent createEvent(int virtualViewId, int eventType) {
+ switch (virtualViewId) {
+ case View.NO_ID:
+ return createEventForHost(eventType);
+ default:
+ return createEventForChild(virtualViewId, eventType);
+ }
+ }
+
+ /**
+ * Constructs and returns an {@link AccessibilityEvent} for the host node.
+ *
+ * @param eventType The type of event to construct.
+ * @return An {@link AccessibilityEvent} populated with information about
+ * the specified item.
+ */
+ private AccessibilityEvent createEventForHost(int eventType) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ onInitializeAccessibilityEvent(mView, event);
+ return event;
+ }
+
+ /**
+ * Constructs and returns an {@link AccessibilityEvent} populated with
+ * information about the specified item.
+ *
+ * @param virtualViewId The virtual view id for the item for which to
+ * construct an event.
+ * @param eventType The type of event to construct.
+ * @return An {@link AccessibilityEvent} populated with information about
+ * the specified item.
+ */
+ private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ event.setEnabled(true);
+ event.setClassName(DEFAULT_CLASS_NAME);
+
+ // Allow the client to populate the event.
+ onPopulateEventForVirtualView(virtualViewId, event);
+
+ // Make sure the developer is following the rules.
+ if (event.getText().isEmpty() && (event.getContentDescription() == null)) {
+ throw new RuntimeException("Callbacks must add text or a content description in "
+ + "populateEventForVirtualViewId()");
+ }
+
+ // Don't allow the client to override these properties.
+ event.setPackageName(mView.getContext().getPackageName());
+ event.setSource(mView, virtualViewId);
+
+ return event;
+ }
+
+ /**
+ * Constructs and returns an {@link android.view.accessibility.AccessibilityNodeInfo} for the
+ * specified virtual view id, which includes the host view
+ * ({@link View#NO_ID}).
+ *
+ * @param virtualViewId The virtual view id for the item for which to
+ * construct a node.
+ * @return An {@link android.view.accessibility.AccessibilityNodeInfo} populated with information
+ * about the specified item.
+ */
+ private AccessibilityNodeInfo createNode(int virtualViewId) {
+ switch (virtualViewId) {
+ case View.NO_ID:
+ return createNodeForHost();
+ default:
+ return createNodeForChild(virtualViewId);
+ }
+ }
+
+ /**
+ * Constructs and returns an {@link AccessibilityNodeInfo} for the
+ * host view populated with its virtual descendants.
+ *
+ * @return An {@link AccessibilityNodeInfo} for the parent node.
+ */
+ private AccessibilityNodeInfo createNodeForHost() {
+ final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView);
+ onInitializeAccessibilityNodeInfo(mView, node);
+
+ // Add the virtual descendants.
+ final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
+ getVisibleVirtualViews(virtualViewIds);
+
+ for (Integer childVirtualViewId : virtualViewIds) {
+ node.addChild(mView, childVirtualViewId);
+ }
+
+ return node;
+ }
+
+ /**
+ * Constructs and returns an {@link AccessibilityNodeInfo} for the
+ * specified item. Automatically manages accessibility focus actions.
+ * <p>
+ * Allows the implementing class to specify most node properties, but
+ * overrides the following:
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#setPackageName}
+ * <li>{@link AccessibilityNodeInfo#setClassName}
+ * <li>{@link AccessibilityNodeInfo#setParent(View)}
+ * <li>{@link AccessibilityNodeInfo#setSource(View, int)}
+ * <li>{@link AccessibilityNodeInfo#setVisibleToUser}
+ * <li>{@link AccessibilityNodeInfo#setBoundsInScreen(Rect)}
+ * </ul>
+ * <p>
+ * Uses the bounds of the parent view and the parent-relative bounding
+ * rectangle specified by
+ * {@link AccessibilityNodeInfo#getBoundsInParent} to automatically
+ * update the following properties:
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#setVisibleToUser}
+ * <li>{@link AccessibilityNodeInfo#setBoundsInParent}
+ * </ul>
+ *
+ * @param virtualViewId The virtual view id for item for which to construct
+ * a node.
+ * @return An {@link AccessibilityNodeInfo} for the specified item.
+ */
+ private AccessibilityNodeInfo createNodeForChild(int virtualViewId) {
+ final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+
+ // Ensure the client has good defaults.
+ node.setEnabled(true);
+ node.setClassName(DEFAULT_CLASS_NAME);
+
+ // Allow the client to populate the node.
+ onPopulateNodeForVirtualView(virtualViewId, node);
+
+ // Make sure the developer is following the rules.
+ if ((node.getText() == null) && (node.getContentDescription() == null)) {
+ throw new RuntimeException("Callbacks must add text or a content description in "
+ + "populateNodeForVirtualViewId()");
+ }
+
+ node.getBoundsInParent(mTempParentRect);
+ if (mTempParentRect.isEmpty()) {
+ throw new RuntimeException("Callbacks must set parent bounds in "
+ + "populateNodeForVirtualViewId()");
+ }
+
+ final int actions = node.getActions();
+ if ((actions & AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) != 0) {
+ throw new RuntimeException("Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in "
+ + "populateNodeForVirtualViewId()");
+ }
+ if ((actions & AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) != 0) {
+ throw new RuntimeException("Callbacks must not add ACTION_CLEAR_ACCESSIBILITY_FOCUS in "
+ + "populateNodeForVirtualViewId()");
+ }
+
+ // Don't allow the client to override these properties.
+ node.setPackageName(mView.getContext().getPackageName());
+ node.setSource(mView, virtualViewId);
+ node.setParent(mView);
+
+ // Manage internal accessibility focus state.
+ if (mFocusedVirtualViewId == virtualViewId) {
+ node.setAccessibilityFocused(true);
+ node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+ } else {
+ node.setAccessibilityFocused(false);
+ node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ }
+
+ // Set the visibility based on the parent bound.
+ if (intersectVisibleToUser(mTempParentRect)) {
+ node.setVisibleToUser(true);
+ node.setBoundsInParent(mTempParentRect);
+ }
+
+ // Calculate screen-relative bound.
+ mView.getLocationOnScreen(mTempGlobalRect);
+ final int offsetX = mTempGlobalRect[0];
+ final int offsetY = mTempGlobalRect[1];
+ mTempScreenRect.set(mTempParentRect);
+ mTempScreenRect.offset(offsetX, offsetY);
+ node.setBoundsInScreen(mTempScreenRect);
+
+ return node;
+ }
+
+ private boolean performAction(int virtualViewId, int action, Bundle arguments) {
+ switch (virtualViewId) {
+ case View.NO_ID:
+ return performActionForHost(action, arguments);
+ default:
+ return performActionForChild(virtualViewId, action, arguments);
+ }
+ }
+
+ private boolean performActionForHost(int action, Bundle arguments) {
+ return performAccessibilityAction(mView, action, arguments);
+ }
+
+ private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+ case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ return manageFocusForChild(virtualViewId, action, arguments);
+ default:
+ return onPerformActionForVirtualView(virtualViewId, action, arguments);
+ }
+ }
+
+ private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+ return requestAccessibilityFocus(virtualViewId);
+ case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ return clearAccessibilityFocus(virtualViewId);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Computes whether the specified {@link Rect} intersects with the visible
+ * portion of its parent {@link View}. Modifies {@code localRect} to contain
+ * only the visible portion.
+ *
+ * @param localRect A rectangle in local (parent) coordinates.
+ * @return Whether the specified {@link Rect} is visible on the screen.
+ */
+ private boolean intersectVisibleToUser(Rect localRect) {
+ // Missing or empty bounds mean this view is not visible.
+ if ((localRect == null) || localRect.isEmpty()) {
+ return false;
+ }
+
+ // Attached to invisible window means this view is not visible.
+ if (mView.getWindowVisibility() != View.VISIBLE) {
+ return false;
+ }
+
+ // An invisible predecessor means that this view is not visible.
+ ViewParent viewParent = mView.getParent();
+ while (viewParent instanceof View) {
+ final View view = (View) viewParent;
+ if ((view.getAlpha() <= 0) || (view.getVisibility() != View.VISIBLE)) {
+ return false;
+ }
+ viewParent = view.getParent();
+ }
+
+ // A null parent implies the view is not visible.
+ if (viewParent == null) {
+ return false;
+ }
+
+ // If no portion of the parent is visible, this view is not visible.
+ if (!mView.getLocalVisibleRect(mTempVisibleRect)) {
+ return false;
+ }
+
+ // Check if the view intersects the visible portion of the parent.
+ return localRect.intersect(mTempVisibleRect);
+ }
+
+ /**
+ * Returns whether this virtual view is accessibility focused.
+ *
+ * @return True if the view is accessibility focused.
+ */
+ private boolean isAccessibilityFocused(int virtualViewId) {
+ return (mFocusedVirtualViewId == virtualViewId);
+ }
+
+ /**
+ * Attempts to give accessibility focus to a virtual view.
+ * <p>
+ * A virtual view will not actually take focus if
+ * {@link AccessibilityManager#isEnabled()} returns false,
+ * {@link AccessibilityManager#isTouchExplorationEnabled()} returns false,
+ * or the view already has accessibility focus.
+ *
+ * @param virtualViewId The id of the virtual view on which to place
+ * accessibility focus.
+ * @return Whether this virtual view actually took accessibility focus.
+ */
+ private boolean requestAccessibilityFocus(int virtualViewId) {
+ final AccessibilityManager accessibilityManager =
+ (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+ if (!mManager.isEnabled()
+ || !accessibilityManager.isTouchExplorationEnabled()) {
+ return false;
+ }
+ // TODO: Check virtual view visibility.
+ if (!isAccessibilityFocused(virtualViewId)) {
+ mFocusedVirtualViewId = virtualViewId;
+ // TODO: Only invalidate virtual view bounds.
+ mView.invalidate();
+ sendEventForVirtualView(virtualViewId,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to clear accessibility focus from a virtual view.
+ *
+ * @param virtualViewId The id of the virtual view from which to clear
+ * accessibility focus.
+ * @return Whether this virtual view actually cleared accessibility focus.
+ */
+ private boolean clearAccessibilityFocus(int virtualViewId) {
+ if (isAccessibilityFocused(virtualViewId)) {
+ mFocusedVirtualViewId = INVALID_ID;
+ mView.invalidate();
+ sendEventForVirtualView(virtualViewId,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Provides a mapping between view-relative coordinates and logical
+ * items.
+ *
+ * @param x The view-relative x coordinate
+ * @param y The view-relative y coordinate
+ * @return virtual view identifier for the logical item under
+ * coordinates (x,y)
+ */
+ protected abstract int getVirtualViewAt(float x, float y);
+
+ /**
+ * Populates a list with the view's visible items. The ordering of items
+ * within {@code virtualViewIds} specifies order of accessibility focus
+ * traversal.
+ *
+ * @param virtualViewIds The list to populate with visible items
+ */
+ protected abstract void getVisibleVirtualViews(List<Integer> virtualViewIds);
+
+ /**
+ * Populates an {@link AccessibilityEvent} with information about the
+ * specified item.
+ * <p>
+ * Implementations <b>must</b> populate the following required fields:
+ * <ul>
+ * <li>event text, see {@link AccessibilityEvent#getText} or
+ * {@link AccessibilityEvent#setContentDescription}
+ * </ul>
+ * <p>
+ * The helper class automatically populates the following fields with
+ * default values, but implementations may optionally override them:
+ * <ul>
+ * <li>item class name, set to android.view.View, see
+ * {@link AccessibilityEvent#setClassName}
+ * </ul>
+ * <p>
+ * The following required fields are automatically populated by the
+ * helper class and may not be overridden:
+ * <ul>
+ * <li>package name, set to the package of the host view's
+ * {@link Context}, see {@link AccessibilityEvent#setPackageName}
+ * <li>event source, set to the host view and virtual view identifier,
+ * see {@link AccessibilityRecord#setSource(View, int)}
+ * </ul>
+ *
+ * @param virtualViewId The virtual view id for the item for which to
+ * populate the event
+ * @param event The event to populate
+ */
+ protected abstract void onPopulateEventForVirtualView(
+ int virtualViewId, AccessibilityEvent event);
+
+ /**
+ * Populates an {@link AccessibilityNodeInfo} with information
+ * about the specified item.
+ * <p>
+ * Implementations <b>must</b> populate the following required fields:
+ * <ul>
+ * <li>event text, see {@link AccessibilityNodeInfo#setText} or
+ * {@link AccessibilityNodeInfo#setContentDescription}
+ * <li>bounds in parent coordinates, see
+ * {@link AccessibilityNodeInfo#setBoundsInParent}
+ * </ul>
+ * <p>
+ * The helper class automatically populates the following fields with
+ * default values, but implementations may optionally override them:
+ * <ul>
+ * <li>enabled state, set to true, see
+ * {@link AccessibilityNodeInfo#setEnabled}
+ * <li>item class name, identical to the class name set by
+ * {@link #onPopulateEventForVirtualView}, see
+ * {@link AccessibilityNodeInfo#setClassName}
+ * </ul>
+ * <p>
+ * The following required fields are automatically populated by the
+ * helper class and may not be overridden:
+ * <ul>
+ * <li>package name, identical to the package name set by
+ * {@link #onPopulateEventForVirtualView}, see
+ * {@link AccessibilityNodeInfo#setPackageName}
+ * <li>node source, identical to the event source set in
+ * {@link #onPopulateEventForVirtualView}, see
+ * {@link AccessibilityNodeInfo#setSource(View, int)}
+ * <li>parent view, set to the host view, see
+ * {@link AccessibilityNodeInfo#setParent(View)}
+ * <li>visibility, computed based on parent-relative bounds, see
+ * {@link AccessibilityNodeInfo#setVisibleToUser}
+ * <li>accessibility focus, computed based on internal helper state, see
+ * {@link AccessibilityNodeInfo#setAccessibilityFocused}
+ * <li>bounds in screen coordinates, computed based on host view bounds,
+ * see {@link AccessibilityNodeInfo#setBoundsInScreen}
+ * </ul>
+ * <p>
+ * Additionally, the helper class automatically handles accessibility
+ * focus management by adding the appropriate
+ * {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS} or
+ * {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
+ * action. Implementations must <b>never</b> manually add these actions.
+ * <p>
+ * The helper class also automatically modifies parent- and
+ * screen-relative bounds to reflect the portion of the item visible
+ * within its parent.
+ *
+ * @param virtualViewId The virtual view identifier of the item for
+ * which to populate the node
+ * @param node The node to populate
+ */
+ protected abstract void onPopulateNodeForVirtualView(
+ int virtualViewId, AccessibilityNodeInfo node);
+
+ /**
+ * Performs the specified accessibility action on the item associated
+ * with the virtual view identifier. See
+ * {@link AccessibilityNodeInfo#performAction(int, Bundle)} for
+ * more information.
+ * <p>
+ * Implementations <b>must</b> handle any actions added manually in
+ * {@link #onPopulateNodeForVirtualView}.
+ * <p>
+ * The helper class automatically handles focus management resulting
+ * from {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}
+ * and
+ * {@link AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
+ * actions.
+ *
+ * @param virtualViewId The virtual view identifier of the item on which
+ * to perform the action
+ * @param action The accessibility action to perform
+ * @param arguments (Optional) A bundle with additional arguments, or
+ * null
+ * @return true if the action was performed
+ */
+ protected abstract boolean onPerformActionForVirtualView(
+ int virtualViewId, int action, Bundle arguments);
+
+ /**
+ * Exposes a virtual view hierarchy to the accessibility framework. Only
+ * used in API 16+.
+ */
+ private class ExploreByTouchNodeProvider extends AccessibilityNodeProvider {
+ @Override
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+ return ExploreByTouchHelper.this.createNode(virtualViewId);
+ }
+
+ @Override
+ public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+ return ExploreByTouchHelper.this.performAction(virtualViewId, action, arguments);
+ }
+ }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 0e22174..f65aab5 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -90,12 +90,12 @@
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
+ android_graphics_Canvas.cpp \
android_graphics_Picture.cpp \
android/graphics/AutoDecodeCancel.cpp \
android/graphics/Bitmap.cpp \
android/graphics/BitmapFactory.cpp \
android/graphics/Camera.cpp \
- android/graphics/Canvas.cpp \
android/graphics/CanvasProperty.cpp \
android/graphics/ColorFilter.cpp \
android/graphics/DrawFilter.cpp \
@@ -122,6 +122,7 @@
android/graphics/Rasterizer.cpp \
android/graphics/Region.cpp \
android/graphics/Shader.cpp \
+ android/graphics/SkiaCanvas.cpp \
android/graphics/SurfaceTexture.cpp \
android/graphics/Typeface.cpp \
android/graphics/TypefaceImpl.cpp \
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index b8fcff6..a890eb4 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -39,14 +39,12 @@
jfieldID gOptions_mimeFieldID;
jfieldID gOptions_mCancelID;
jfieldID gOptions_bitmapFieldID;
-jfieldID gBitmap_nativeBitmapFieldID;
-jfieldID gBitmap_layoutBoundsFieldID;
-#if 0
- #define TRACE_BITMAP(code) code
-#else
- #define TRACE_BITMAP(code)
-#endif
+jfieldID gBitmap_nativeBitmapFieldID;
+jfieldID gBitmap_ninePatchInsetsFieldID;
+
+jclass gInsetStruct_class;
+jmethodID gInsetStruct_constructorMethodID;
using namespace android;
@@ -195,8 +193,7 @@
const unsigned int mSize;
};
-static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
- jobject options) {
+static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
int sampleSize = 1;
@@ -331,12 +328,12 @@
}
jbyteArray ninePatchChunk = NULL;
- if (peeker.fPatch != NULL) {
+ if (peeker.mPatch != NULL) {
if (willScale) {
- scaleNinePatchChunk(peeker.fPatch, scale);
+ scaleNinePatchChunk(peeker.mPatch, scale);
}
- size_t ninePatchArraySize = peeker.fPatch->serializedSize();
+ size_t ninePatchArraySize = peeker.mPatch->serializedSize();
ninePatchChunk = env->NewByteArray(ninePatchArraySize);
if (ninePatchChunk == NULL) {
return nullObjectReturn("ninePatchChunk == null");
@@ -347,28 +344,18 @@
return nullObjectReturn("primitive array == null");
}
- memcpy(array, peeker.fPatch, peeker.fPatchSize);
+ memcpy(array, peeker.mPatch, peeker.mPatchSize);
env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
}
- jintArray layoutBounds = NULL;
- if (peeker.fLayoutBounds != NULL) {
- layoutBounds = env->NewIntArray(4);
- if (layoutBounds == NULL) {
- return nullObjectReturn("layoutBounds == null");
- }
-
- jint scaledBounds[4];
- if (willScale) {
- for (int i=0; i<4; i++) {
- scaledBounds[i] = (jint)((((jint*)peeker.fLayoutBounds)[i]*scale) + .5f);
- }
- } else {
- memcpy(scaledBounds, (jint*)peeker.fLayoutBounds, sizeof(scaledBounds));
- }
- env->SetIntArrayRegion(layoutBounds, 0, 4, scaledBounds);
+ jobject ninePatchInsets = NULL;
+ if (peeker.mHasInsets) {
+ ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
+ peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
+ peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
+ peeker.mOutlineRadius, peeker.mOutlineFilled, scale);
if (javaBitmap != NULL) {
- env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds);
+ env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
}
}
@@ -409,10 +396,10 @@
}
if (padding) {
- if (peeker.fPatch != NULL) {
+ if (peeker.mPatch != NULL) {
GraphicsJNI::set_jrect(env, padding,
- peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
- peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
+ peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
+ peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
} else {
GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
}
@@ -446,7 +433,7 @@
// now create the java bitmap
return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(),
- bitmapCreateFlags, ninePatchChunk, layoutBounds, -1);
+ bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}
// Need to buffer enough input to be able to rewind as much as might be read by a decoder
@@ -576,7 +563,7 @@
jclass options_class = env->FindClass("android/graphics/BitmapFactory$Options");
SkASSERT(options_class);
gOptions_bitmapFieldID = getFieldIDCheck(env, options_class, "inBitmap",
- "Landroid/graphics/Bitmap;");
+ "Landroid/graphics/Bitmap;");
gOptions_justBoundsFieldID = getFieldIDCheck(env, options_class, "inJustDecodeBounds", "Z");
gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I");
gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig",
@@ -598,7 +585,12 @@
jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
SkASSERT(bitmap_class);
gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "J");
- gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mOpticalInsets", "[I");
+ gBitmap_ninePatchInsetsFieldID = getFieldIDCheck(env, bitmap_class, "mNinePatchInsets",
+ "Landroid/graphics/NinePatch$InsetStruct;");
+
+ gInsetStruct_class = (jclass) env->NewGlobalRef(env->FindClass("android/graphics/NinePatch$InsetStruct"));
+ gInsetStruct_constructorMethodID = env->GetMethodID(gInsetStruct_class, "<init>", "(IIIIIIIIFZF)V");
+
int ret = AndroidRuntime::registerNativeMethods(env,
"android/graphics/BitmapFactory$Options",
gOptionsMethods,
diff --git a/core/jni/android/graphics/Camera.cpp b/core/jni/android/graphics/Camera.cpp
index d17f46c..9f832b0 100644
--- a/core/jni/android/graphics/Camera.cpp
+++ b/core/jni/android/graphics/Camera.cpp
@@ -3,6 +3,7 @@
#include "SkCamera.h"
+#include "Canvas.h"
#include "GraphicsJNI.h"
static jfieldID gNativeInstanceFieldID;
@@ -95,10 +96,10 @@
}
static void Camera_applyToCanvas(JNIEnv* env, jobject obj, jlong canvasHandle) {
- SkCanvas* native_canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
+ SkCanvas* canvas = reinterpret_cast<android::Canvas*>(canvasHandle)->getSkCanvas();
jlong viewHandle = env->GetLongField(obj, gNativeInstanceFieldID);
Sk3DView* v = reinterpret_cast<Sk3DView*>(viewHandle);
- v->applyToCanvas((SkCanvas*)native_canvas);
+ v->applyToCanvas(canvas);
}
static jfloat Camera_dotWithNormal(JNIEnv* env, jobject obj,
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
deleted file mode 100644
index 6254f3d..0000000
--- a/core/jni/android/graphics/Canvas.cpp
+++ /dev/null
@@ -1,1330 +0,0 @@
-/*
- * Copyright (C) 2006-2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "jni.h"
-#include "GraphicsJNI.h"
-#include <android_runtime/AndroidRuntime.h>
-
-#include "SkCanvas.h"
-#include "SkClipStack.h"
-#include "SkDevice.h"
-#include "SkDeque.h"
-#include "SkDrawFilter.h"
-#include "SkGraphics.h"
-#include <SkImageInfo.h>
-#include "SkPorterDuff.h"
-#include "SkShader.h"
-#include "SkTArray.h"
-#include "SkTemplates.h"
-
-#include <minikin/Layout.h>
-#include "MinikinSkia.h"
-#include "MinikinUtils.h"
-
-#include "TypefaceImpl.h"
-
-#include "unicode/ubidi.h"
-#include "unicode/ushape.h"
-
-#include <utils/Log.h>
-
-namespace android {
-
-class ClipCopier : public SkCanvas::ClipVisitor {
-public:
- ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {}
-
- virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) {
- m_dstCanvas->clipRect(rect, op, antialias);
- }
- virtual void clipRRect(const SkRRect& rrect, SkRegion::Op op, bool antialias) {
- m_dstCanvas->clipRRect(rrect, op, antialias);
- }
- virtual void clipPath(const SkPath& path, SkRegion::Op op, bool antialias) {
- m_dstCanvas->clipPath(path, op, antialias);
- }
-
-private:
- SkCanvas* m_dstCanvas;
-};
-
-// Holds an SkCanvas reference plus additional native data.
-class NativeCanvasWrapper {
-private:
- struct SaveRec {
- int saveCount;
- SkCanvas::SaveFlags saveFlags;
- };
-
-public:
- NativeCanvasWrapper(SkCanvas* canvas)
- : mCanvas(canvas)
- , mSaveStack(NULL) {
- SkASSERT(canvas);
- }
-
- ~NativeCanvasWrapper() {
- delete mSaveStack;
- }
-
- SkCanvas* getCanvas() const {
- return mCanvas.get();
- }
-
- void setCanvas(SkCanvas* canvas) {
- SkASSERT(canvas);
- mCanvas.reset(canvas);
-
- delete mSaveStack;
- mSaveStack = NULL;
- }
-
- int save(SkCanvas::SaveFlags flags) {
- int count = mCanvas->save();
- recordPartialSave(flags);
- return count;
- }
-
- int saveLayer(const SkRect* bounds, const SkPaint* paint,
- SkCanvas::SaveFlags flags) {
- int count = mCanvas->saveLayer(bounds, paint,
- static_cast<SkCanvas::SaveFlags>(flags | SkCanvas::kMatrixClip_SaveFlag));
- recordPartialSave(flags);
- return count;
- }
-
- int saveLayerAlpha(const SkRect* bounds, U8CPU alpha,
- SkCanvas::SaveFlags flags) {
- int count = mCanvas->saveLayerAlpha(bounds, alpha,
- static_cast<SkCanvas::SaveFlags>(flags | SkCanvas::kMatrixClip_SaveFlag));
- recordPartialSave(flags);
- return count;
- }
-
- void restore() {
- const SaveRec* rec = (NULL == mSaveStack)
- ? NULL
- : static_cast<SaveRec*>(mSaveStack->back());
- int currentSaveCount = mCanvas->getSaveCount() - 1;
- SkASSERT(NULL == rec || currentSaveCount >= rec->saveCount);
-
- if (NULL == rec || rec->saveCount != currentSaveCount) {
- // Fast path - no record for this frame.
- mCanvas->restore();
- return;
- }
-
- bool preserveMatrix = !(rec->saveFlags & SkCanvas::kMatrix_SaveFlag);
- bool preserveClip = !(rec->saveFlags & SkCanvas::kClip_SaveFlag);
-
- SkMatrix savedMatrix;
- if (preserveMatrix) {
- savedMatrix = mCanvas->getTotalMatrix();
- }
-
- SkTArray<SkClipStack::Element> savedClips;
- if (preserveClip) {
- saveClipsForFrame(savedClips, currentSaveCount);
- }
-
- mCanvas->restore();
-
- if (preserveMatrix) {
- mCanvas->setMatrix(savedMatrix);
- }
-
- if (preserveClip && !savedClips.empty()) {
- applyClips(savedClips);
- }
-
- mSaveStack->pop_back();
- }
-
-private:
- void recordPartialSave(SkCanvas::SaveFlags flags) {
- // A partial save is a save operation which doesn't capture the full canvas state.
- // (either kMatrix_SaveFlags or kClip_SaveFlag is missing).
-
- // Mask-out non canvas state bits.
- flags = static_cast<SkCanvas::SaveFlags>(flags & SkCanvas::kMatrixClip_SaveFlag);
-
- if (SkCanvas::kMatrixClip_SaveFlag == flags) {
- // not a partial save.
- return;
- }
-
- if (NULL == mSaveStack) {
- mSaveStack = new SkDeque(sizeof(struct SaveRec), 8);
- }
-
- SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back());
- // Store the save counter in the SkClipStack domain.
- // (0-based, equal to the number of save ops on the stack).
- rec->saveCount = mCanvas->getSaveCount() - 1;
- rec->saveFlags = flags;
- }
-
- void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips,
- int frameSaveCount) {
- SkClipStack::Iter clipIterator(*mCanvas->getClipStack(),
- SkClipStack::Iter::kTop_IterStart);
- while (const SkClipStack::Element* elem = clipIterator.next()) {
- if (elem->getSaveCount() < frameSaveCount) {
- // done with the current frame.
- break;
- }
- SkASSERT(elem->getSaveCount() == frameSaveCount);
- clips.push_back(*elem);
- }
- }
-
- void applyClips(const SkTArray<SkClipStack::Element>& clips) {
- ClipCopier clipCopier(mCanvas);
-
- // The clip stack stores clips in device space.
- SkMatrix origMatrix = mCanvas->getTotalMatrix();
- mCanvas->resetMatrix();
-
- // We pushed the clips in reverse order.
- for (int i = clips.count() - 1; i >= 0; --i) {
- clips[i].replay(&clipCopier);
- }
-
- mCanvas->setMatrix(origMatrix);
- }
-
- SkAutoTUnref<SkCanvas> mCanvas;
- SkDeque* mSaveStack; // lazily allocated, tracks partial saves.
-};
-
-// Returns true if the SkCanvas's clip is non-empty.
-static jboolean hasNonEmptyClip(const SkCanvas& canvas) {
- bool emptyClip = canvas.isClipEmpty();
- return emptyClip ? JNI_FALSE : JNI_TRUE;
-}
-
-class SkCanvasGlue {
-public:
- // Get the native wrapper for a given handle.
- static inline NativeCanvasWrapper* getNativeWrapper(jlong nativeHandle) {
- SkASSERT(nativeHandle);
- return reinterpret_cast<NativeCanvasWrapper*>(nativeHandle);
- }
-
- // Get the SkCanvas for a given native handle.
- static inline SkCanvas* getNativeCanvas(jlong nativeHandle) {
- NativeCanvasWrapper* wrapper = getNativeWrapper(nativeHandle);
- SkCanvas* canvas = wrapper->getCanvas();
- SkASSERT(canvas);
-
- return canvas;
- }
-
- // Construct an SkCanvas from the bitmap.
- static SkCanvas* createCanvas(SkBitmap* bitmap) {
- if (bitmap) {
- return SkNEW_ARGS(SkCanvas, (*bitmap));
- }
-
- // Create an empty bitmap device to prevent callers from crashing
- // if they attempt to draw into this canvas.
- SkBitmap emptyBitmap;
- return new SkCanvas(emptyBitmap);
- }
-
- // Copy the canvas matrix & clip state.
- static void copyCanvasState(SkCanvas* srcCanvas, SkCanvas* dstCanvas) {
- if (srcCanvas && dstCanvas) {
- dstCanvas->setMatrix(srcCanvas->getTotalMatrix());
- if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) {
- ClipCopier copier(dstCanvas);
- srcCanvas->replayClips(&copier);
- }
- }
- }
-
- // Native JNI handlers
- static void finalizer(JNIEnv* env, jobject clazz, jlong nativeHandle) {
- NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(nativeHandle);
- delete wrapper;
- }
-
- // Native wrapper constructor used by Canvas(Bitmap)
- static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) {
- // No check - 0 is a valid bitmapHandle.
- SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
- SkCanvas* canvas = createCanvas(bitmap);
-
- return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas));
- }
-
- // Native wrapper constructor used by Canvas(native_canvas)
- static jlong initCanvas(JNIEnv* env, jobject, jlong canvasHandle) {
- SkCanvas* canvas = reinterpret_cast<SkCanvas*>(canvasHandle);
- return reinterpret_cast<jlong>(new NativeCanvasWrapper(canvas));
- }
-
- // Set the given bitmap as the new draw target (wrapped in a new SkCanvas),
- // optionally copying canvas matrix & clip state.
- static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
- jboolean copyState) {
- NativeCanvasWrapper* wrapper = reinterpret_cast<NativeCanvasWrapper*>(canvasHandle);
- SkCanvas* newCanvas = createCanvas(reinterpret_cast<SkBitmap*>(bitmapHandle));
- NPE_CHECK_RETURN_VOID(env, newCanvas);
-
- if (copyState == JNI_TRUE) {
- copyCanvasState(wrapper->getCanvas(), newCanvas);
- }
-
- // setCanvas() unrefs the old canvas.
- wrapper->setCanvas(newCanvas);
- }
-
- static void freeCaches(JNIEnv* env, jobject) {
- SkGraphics::PurgeFontCache();
- }
-
- static void freeTextLayoutCaches(JNIEnv* env, jobject) {
- Layout::purgeCaches();
- }
-
- static jboolean isOpaque(JNIEnv*, jobject, jlong canvasHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- bool result = canvas->getDevice()->accessBitmap(false).isOpaque();
- return result ? JNI_TRUE : JNI_FALSE;
- }
-
- static jint getWidth(JNIEnv*, jobject, jlong canvasHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- int width = canvas->getDevice()->accessBitmap(false).width();
- return static_cast<jint>(width);
- }
-
- static jint getHeight(JNIEnv*, jobject, jlong canvasHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- int height = canvas->getDevice()->accessBitmap(false).height();
- return static_cast<jint>(height);
- }
-
- static jint save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) {
- NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
- SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
- return static_cast<jint>(wrapper->save(flags));
- }
-
- static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle,
- jfloat l, jfloat t, jfloat r, jfloat b,
- jlong paintHandle, jint flagsHandle) {
- NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
- SkRect bounds;
- bounds.set(l, t, r, b);
- return static_cast<jint>(wrapper->saveLayer(&bounds, paint, flags));
- }
-
- static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle,
- jfloat l, jfloat t, jfloat r, jfloat b,
- jint alpha, jint flagsHandle) {
- NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
- SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
- SkRect bounds;
- bounds.set(l, t, r, b);
- return static_cast<jint>(wrapper->saveLayerAlpha(&bounds, alpha, flags));
- }
-
- static void restore(JNIEnv* env, jobject, jlong canvasHandle) {
- NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
- if (wrapper->getCanvas()->getSaveCount() <= 1) { // cannot restore anymore
- doThrowISE(env, "Underflow in restore");
- return;
- }
- wrapper->restore();
- }
-
- static jint getSaveCount(JNIEnv*, jobject, jlong canvasHandle) {
- return static_cast<jint>(getNativeCanvas(canvasHandle)->getSaveCount());
- }
-
- static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle,
- jint restoreCount) {
- NativeCanvasWrapper* wrapper = getNativeWrapper(canvasHandle);
- if (restoreCount < 1) {
- doThrowIAE(env, "Underflow in restoreToCount");
- return;
- }
-
- while (wrapper->getCanvas()->getSaveCount() > restoreCount) {
- wrapper->restore();
- }
- }
-
- static void translate(JNIEnv*, jobject, jlong canvasHandle,
- jfloat dx, jfloat dy) {
- getNativeCanvas(canvasHandle)->translate(dx, dy);
- }
-
- static void scale__FF(JNIEnv*, jobject, jlong canvasHandle,
- jfloat sx, jfloat sy) {
- getNativeCanvas(canvasHandle)->scale(sx, sy);
- }
-
- static void rotate__F(JNIEnv*, jobject, jlong canvasHandle,
- jfloat degrees) {
- getNativeCanvas(canvasHandle)->rotate(degrees);
- }
-
- static void skew__FF(JNIEnv*, jobject, jlong canvasHandle,
- jfloat sx, jfloat sy) {
- getNativeCanvas(canvasHandle)->skew(sx, sy);
- }
-
- static void concat(JNIEnv* env, jobject, jlong canvasHandle,
- jlong matrixHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- canvas->concat(*matrix);
- }
-
- static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle,
- jlong matrixHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- if (NULL == matrix) {
- canvas->resetMatrix();
- } else {
- canvas->setMatrix(*matrix);
- }
- }
-
- static jboolean clipRect(JNIEnv*, jobject, jlong canvasHandle,
- jfloat left, jfloat top, jfloat right,
- jfloat bottom, jint op) {
- SkRect r;
- r.set(left, top, right, bottom);
- SkCanvas* c = getNativeCanvas(canvasHandle);
- c->clipRect(r, static_cast<SkRegion::Op>(op));
- return hasNonEmptyClip(*c);
- }
-
- static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle,
- jlong pathHandle, jint op) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- canvas->clipPath(*reinterpret_cast<SkPath*>(pathHandle),
- static_cast<SkRegion::Op>(op));
- return hasNonEmptyClip(*canvas);
- }
-
- static jboolean clipRegion(JNIEnv* env, jobject, jlong canvasHandle,
- jlong deviceRgnHandle, jint op) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkRegion* deviceRgn = reinterpret_cast<SkRegion*>(deviceRgnHandle);
- SkPath rgnPath;
- if (deviceRgn->getBoundaryPath(&rgnPath)) {
- // The region is specified in device space.
- SkMatrix savedMatrix = canvas->getTotalMatrix();
- canvas->resetMatrix();
- canvas->clipPath(rgnPath, static_cast<SkRegion::Op>(op));
- canvas->setMatrix(savedMatrix);
- } else {
- canvas->clipRect(SkRect::MakeEmpty(), static_cast<SkRegion::Op>(op));
- }
- return hasNonEmptyClip(*canvas);
- }
-
- static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle,
- jlong filterHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- canvas->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
- }
-
- static jboolean quickReject__Path(JNIEnv* env, jobject, jlong canvasHandle,
- jlong pathHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- bool result = canvas->quickReject(*reinterpret_cast<SkPath*>(pathHandle));
- return result ? JNI_TRUE : JNI_FALSE;
- }
-
- static jboolean quickReject__FFFF(JNIEnv* env, jobject, jlong canvasHandle,
- jfloat left, jfloat top, jfloat right,
- jfloat bottom) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkRect r;
- r.set(left, top, right, bottom);
- bool result = canvas->quickReject(r);
- return result ? JNI_TRUE : JNI_FALSE;
- }
-
- static void drawRGB(JNIEnv* env, jobject, jlong canvasHandle,
- jint r, jint g, jint b) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- canvas->drawARGB(0xFF, r, g, b);
- }
-
- static void drawARGB(JNIEnv* env, jobject, jlong canvasHandle,
- jint a, jint r, jint g, jint b) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- canvas->drawARGB(a, r, g, b);
- }
-
- static void drawColor__I(JNIEnv* env, jobject, jlong canvasHandle,
- jint color) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- canvas->drawColor(color);
- }
-
- static void drawColor__II(JNIEnv* env, jobject, jlong canvasHandle,
- jint color, jint modeHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle);
- canvas->drawColor(color, SkPorterDuff::ToXfermodeMode(mode));
- }
-
- static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle,
- jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- canvas->drawPaint(*paint);
- }
-
- static void doPoints(JNIEnv* env, jlong canvasHandle,
- jfloatArray jptsArray, jint offset, jint count,
- jlong paintHandle, jint modeHandle) {
- NPE_CHECK_RETURN_VOID(env, jptsArray);
- SkCanvas::PointMode mode = static_cast<SkCanvas::PointMode>(modeHandle);
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-
- AutoJavaFloatArray autoPts(env, jptsArray);
- float* floats = autoPts.ptr();
- const int length = autoPts.length();
-
- if ((offset | count) < 0 || offset + count > length) {
- doThrowAIOOBE(env);
- return;
- }
-
- // now convert the floats into SkPoints
- count >>= 1; // now it is the number of points
- SkAutoSTMalloc<32, SkPoint> storage(count);
- SkPoint* pts = storage.get();
- const float* src = floats + offset;
- for (int i = 0; i < count; i++) {
- pts[i].set(src[0], src[1]);
- src += 2;
- }
- canvas->drawPoints(mode, count, pts, *paint);
- }
-
- static void drawPoints(JNIEnv* env, jobject, jlong canvasHandle,
- jfloatArray jptsArray, jint offset,
- jint count, jlong paintHandle) {
- doPoints(env, canvasHandle, jptsArray, offset, count, paintHandle,
- SkCanvas::kPoints_PointMode);
- }
-
- static void drawLines(JNIEnv* env, jobject, jlong canvasHandle,
- jfloatArray jptsArray, jint offset, jint count,
- jlong paintHandle) {
- doPoints(env, canvasHandle, jptsArray, offset, count, paintHandle,
- SkCanvas::kLines_PointMode);
- }
-
- static void drawPoint(JNIEnv*, jobject, jlong canvasHandle, jfloat x, jfloat y,
- jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- canvas->drawPoint(x, y, *paint);
- }
-
- static void drawLine__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle,
- jfloat startX, jfloat startY, jfloat stopX,
- jfloat stopY, jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- canvas->drawLine(startX, startY, stopX, stopY, *paint);
- }
-
- static void drawRect__FFFFPaint(JNIEnv* env, jobject, jlong canvasHandle,
- jfloat left, jfloat top, jfloat right,
- jfloat bottom, jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- canvas->drawRectCoords(left, top, right, bottom, *paint);
- }
-
- static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
- jfloat right, jfloat bottom, jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
- canvas->drawOval(oval, *paint);
- }
-
- static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx,
- jfloat cy, jfloat radius, jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- canvas->drawCircle(cx, cy, radius, *paint);
- }
-
- static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
- jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle, jboolean useCenter,
- jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
- canvas->drawArc(oval, startAngle, sweepAngle, useCenter, *paint);
- }
-
- static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle,
- jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry,
- jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
- canvas->drawRoundRect(rect, rx, ry, *paint);
- }
-
- static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
- jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- canvas->drawPath(*path, *paint);
- }
-
- static void drawBitmap__BitmapFFPaint(JNIEnv* env, jobject jcanvas,
- jlong canvasHandle, jlong bitmapHandle,
- jfloat left, jfloat top,
- jlong paintHandle, jint canvasDensity,
- jint screenDensity, jint bitmapDensity) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-
- if (canvasDensity == bitmapDensity || canvasDensity == 0
- || bitmapDensity == 0) {
- if (screenDensity != 0 && screenDensity != bitmapDensity) {
- SkPaint filteredPaint;
- if (paint) {
- filteredPaint = *paint;
- }
- filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
- canvas->drawBitmap(*bitmap, left, top, &filteredPaint);
- } else {
- canvas->drawBitmap(*bitmap, left, top, paint);
- }
- } else {
- canvas->save();
- SkScalar scale = canvasDensity / (float)bitmapDensity;
- canvas->translate(left, top);
- canvas->scale(scale, scale);
-
- SkPaint filteredPaint;
- if (paint) {
- filteredPaint = *paint;
- }
- filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
-
- canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint);
-
- canvas->restore();
- }
- }
-
- static void doDrawBitmap(JNIEnv* env, SkCanvas* canvas, SkBitmap* bitmap,
- jobject srcIRect, const SkRect& dst, SkPaint* paint,
- jint screenDensity, jint bitmapDensity) {
- SkIRect src, *srcPtr = NULL;
-
- if (NULL != srcIRect) {
- GraphicsJNI::jrect_to_irect(env, srcIRect, &src);
- srcPtr = &src;
- }
-
- if (screenDensity != 0 && screenDensity != bitmapDensity) {
- SkPaint filteredPaint;
- if (paint) {
- filteredPaint = *paint;
- }
- filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
- canvas->drawBitmapRect(*bitmap, srcPtr, dst, &filteredPaint);
- } else {
- canvas->drawBitmapRect(*bitmap, srcPtr, dst, paint);
- }
- }
-
- static void drawBitmapRF(JNIEnv* env, jobject, jlong canvasHandle,
- jlong bitmapHandle, jobject srcIRect,
- jobject dstRectF, jlong paintHandle,
- jint screenDensity, jint bitmapDensity) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- SkRect dst;
- GraphicsJNI::jrectf_to_rect(env, dstRectF, &dst);
- doDrawBitmap(env, canvas, bitmap, srcIRect, dst, paint,
- screenDensity, bitmapDensity);
- }
-
- static void drawBitmapRR(JNIEnv* env, jobject, jlong canvasHandle,
- jlong bitmapHandle, jobject srcIRect,
- jobject dstRect, jlong paintHandle,
- jint screenDensity, jint bitmapDensity) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- SkRect dst;
- GraphicsJNI::jrect_to_rect(env, dstRect, &dst);
- doDrawBitmap(env, canvas, bitmap, srcIRect, dst, paint,
- screenDensity, bitmapDensity);
- }
-
- static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle,
- jintArray jcolors, jint offset, jint stride,
- jfloat x, jfloat y, jint width, jint height,
- jboolean hasAlpha, jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- // Note: If hasAlpha is false, kRGB_565_SkColorType will be used, which will
- // correct the alphaType to kOpaque_SkAlphaType.
- SkImageInfo info = SkImageInfo::Make(width, height,
- hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
- kPremul_SkAlphaType);
- SkBitmap bitmap;
- if (!bitmap.allocPixels(info)) {
- return;
- }
-
- if (!GraphicsJNI::SetPixels(env, jcolors, offset, stride,
- 0, 0, width, height, bitmap)) {
- return;
- }
-
- canvas->drawBitmap(bitmap, x, y, paint);
- }
-
- static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle,
- jlong bitmapHandle, jlong matrixHandle,
- jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
- const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- canvas->drawBitmapMatrix(*bitmap, *matrix, paint);
- }
-
- static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle,
- jlong bitmapHandle, jint meshWidth, jint meshHeight,
- jfloatArray jverts, jint vertIndex, jintArray jcolors,
- jint colorIndex, jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
- const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-
- const int ptCount = (meshWidth + 1) * (meshHeight + 1);
- const int indexCount = meshWidth * meshHeight * 6;
-
- AutoJavaFloatArray vertA(env, jverts, vertIndex + (ptCount << 1));
- AutoJavaIntArray colorA(env, jcolors, colorIndex + ptCount);
-
- /* Our temp storage holds 2 or 3 arrays.
- texture points [ptCount * sizeof(SkPoint)]
- optionally vertex points [ptCount * sizeof(SkPoint)] if we need a
- copy to convert from float to fixed
- indices [ptCount * sizeof(uint16_t)]
- */
- ssize_t storageSize = ptCount * sizeof(SkPoint); // texs[]
- storageSize += indexCount * sizeof(uint16_t); // indices[]
-
- SkAutoMalloc storage(storageSize);
- SkPoint* texs = (SkPoint*)storage.get();
- SkPoint* verts;
- uint16_t* indices;
-#ifdef SK_SCALAR_IS_FLOAT
- verts = (SkPoint*)(vertA.ptr() + vertIndex);
- indices = (uint16_t*)(texs + ptCount);
-#else
- SkASSERT(false);
-#endif
-
- // cons up texture coordinates and indices
- {
- const SkScalar w = SkIntToScalar(bitmap->width());
- const SkScalar h = SkIntToScalar(bitmap->height());
- const SkScalar dx = w / meshWidth;
- const SkScalar dy = h / meshHeight;
-
- SkPoint* texsPtr = texs;
- SkScalar y = 0;
- for (int i = 0; i <= meshHeight; i++) {
- if (i == meshHeight) {
- y = h; // to ensure numerically we hit h exactly
- }
- SkScalar x = 0;
- for (int j = 0; j < meshWidth; j++) {
- texsPtr->set(x, y);
- texsPtr += 1;
- x += dx;
- }
- texsPtr->set(w, y);
- texsPtr += 1;
- y += dy;
- }
- SkASSERT(texsPtr - texs == ptCount);
- }
-
- // cons up indices
- {
- uint16_t* indexPtr = indices;
- int index = 0;
- for (int i = 0; i < meshHeight; i++) {
- for (int j = 0; j < meshWidth; j++) {
- // lower-left triangle
- *indexPtr++ = index;
- *indexPtr++ = index + meshWidth + 1;
- *indexPtr++ = index + meshWidth + 2;
- // upper-right triangle
- *indexPtr++ = index;
- *indexPtr++ = index + meshWidth + 2;
- *indexPtr++ = index + 1;
- // bump to the next cell
- index += 1;
- }
- // bump to the next row
- index += 1;
- }
- SkASSERT(indexPtr - indices == indexCount);
- SkASSERT((char*)indexPtr - (char*)storage.get() == storageSize);
- }
-
- // double-check that we have legal indices
-#ifdef SK_DEBUG
- {
- for (int i = 0; i < indexCount; i++) {
- SkASSERT((unsigned)indices[i] < (unsigned)ptCount);
- }
- }
-#endif
-
- // cons-up a shader for the bitmap
- SkPaint tmpPaint;
- if (paint) {
- tmpPaint = *paint;
- }
- SkShader* shader = SkShader::CreateBitmapShader(*bitmap,
- SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
- SkSafeUnref(tmpPaint.setShader(shader));
-
- canvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, verts,
- texs, (const SkColor*)colorA.ptr(), NULL, indices,
- indexCount, tmpPaint);
- }
-
- static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
- jint modeHandle, jint vertexCount,
- jfloatArray jverts, jint vertIndex,
- jfloatArray jtexs, jint texIndex,
- jintArray jcolors, jint colorIndex,
- jshortArray jindices, jint indexIndex,
- jint indexCount, jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle);
- const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
-
- AutoJavaFloatArray vertA(env, jverts, vertIndex + vertexCount);
- AutoJavaFloatArray texA(env, jtexs, texIndex + vertexCount);
- AutoJavaIntArray colorA(env, jcolors, colorIndex + vertexCount);
- AutoJavaShortArray indexA(env, jindices, indexIndex + indexCount);
-
- const int ptCount = vertexCount >> 1;
-
- SkPoint* verts;
- SkPoint* texs = NULL;
-#ifdef SK_SCALAR_IS_FLOAT
- verts = (SkPoint*)(vertA.ptr() + vertIndex);
- if (jtexs != NULL) {
- texs = (SkPoint*)(texA.ptr() + texIndex);
- }
-#else
- SkASSERT(false);
-#endif
-
- const SkColor* colors = NULL;
- const uint16_t* indices = NULL;
- if (jcolors != NULL) {
- colors = (const SkColor*)(colorA.ptr() + colorIndex);
- }
- if (jindices != NULL) {
- indices = (const uint16_t*)(indexA.ptr() + indexIndex);
- }
-
- canvas->drawVertices(mode, ptCount, verts, texs, colors, NULL,
- indices, indexCount, *paint);
- }
-
-
- static void drawText___CIIFFIPaintTypeface(JNIEnv* env, jobject, jlong canvasHandle,
- jcharArray text, jint index, jint count,
- jfloat x, jfloat y, jint bidiFlags,
- jlong paintHandle, jlong typefaceHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
- jchar* textArray = env->GetCharArrayElements(text, NULL);
- drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, bidiFlags, paint, typeface);
- env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
- }
-
- static void drawText__StringIIFFIPaintTypeface(JNIEnv* env, jobject,
- jlong canvasHandle, jstring text,
- jint start, jint end,
- jfloat x, jfloat y, jint bidiFlags,
- jlong paintHandle, jlong typefaceHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
- const jchar* textArray = env->GetStringChars(text, NULL);
- drawTextWithGlyphs(canvas, textArray, start, end, x, y, bidiFlags, paint, typeface);
- env->ReleaseStringChars(text, textArray);
- }
-
- class DrawTextFunctor {
- public:
- DrawTextFunctor(const Layout& layout, SkCanvas* canvas, jfloat x, jfloat y, SkPaint* paint,
- uint16_t* glyphs, SkPoint* pos)
- : layout(layout), canvas(canvas), x(x), y(y), paint(paint), glyphs(glyphs),
- pos(pos) { }
-
- void operator()(size_t start, size_t end) {
- for (size_t i = start; i < end; i++) {
- glyphs[i] = layout.getGlyphId(i);
- pos[i].fX = x + layout.getX(i);
- pos[i].fY = y + layout.getY(i);
- }
- canvas->drawPosText(glyphs + start, (end - start) << 1, pos + start, *paint);
- }
- private:
- const Layout& layout;
- SkCanvas* canvas;
- jfloat x;
- jfloat y;
- SkPaint* paint;
- uint16_t* glyphs;
- SkPoint* pos;
- };
-
- static void drawGlyphsToSkia(SkCanvas* canvas, SkPaint* paint, const Layout& layout, float x, float y) {
- size_t nGlyphs = layout.nGlyphs();
- uint16_t* glyphs = new uint16_t[nGlyphs];
- SkPoint* pos = new SkPoint[nGlyphs];
-
- x += MinikinUtils::xOffsetForTextAlign(paint, layout);
- SkPaint::Align align = paint->getTextAlign();
- paint->setTextAlign(SkPaint::kLeft_Align);
- paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
- DrawTextFunctor f(layout, canvas, x, y, paint, glyphs, pos);
- MinikinUtils::forFontRun(layout, paint, f);
- doDrawTextDecorations(canvas, x, y, layout.getAdvance(), paint);
- paint->setTextAlign(align);
- delete[] glyphs;
- delete[] pos;
- }
-
- static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
- int start, int end,
- jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
-
- jint count = end - start;
- drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, bidiFlags, paint,
- typeface);
- }
-
- static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
- int start, int count, int contextCount,
- jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
-
- Layout layout;
- std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
- layout.doLayout(textArray, start, count, contextCount, css);
- drawGlyphsToSkia(canvas, paint, layout, x, y);
- }
-
-// Same values used by Skia
-#define kStdStrikeThru_Offset (-6.0f / 21.0f)
-#define kStdUnderline_Offset (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
- static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat length,
- SkPaint* paint) {
- uint32_t flags;
- SkDrawFilter* drawFilter = canvas->getDrawFilter();
- if (drawFilter) {
- SkPaint paintCopy(*paint);
- drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
- flags = paintCopy.getFlags();
- } else {
- flags = paint->getFlags();
- }
- if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
- SkScalar left = x;
- SkScalar right = x + length;
- float textSize = paint->getTextSize();
- float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
- if (flags & SkPaint::kUnderlineText_Flag) {
- SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
- SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
- canvas->drawRectCoords(left, top, right, bottom, *paint);
- }
- if (flags & SkPaint::kStrikeThruText_Flag) {
- SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
- SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
- canvas->drawRectCoords(left, top, right, bottom, *paint);
- }
- }
- }
-
- static void doDrawGlyphsPos(SkCanvas* canvas, const jchar* glyphArray, const jfloat* posArray,
- int index, int count, jfloat x, jfloat y, SkPaint* paint) {
- SkPoint* posPtr = new SkPoint[count];
- for (int indx = 0; indx < count; indx++) {
- posPtr[indx].fX = x + posArray[indx * 2];
- posPtr[indx].fY = y + posArray[indx * 2 + 1];
- }
- canvas->drawPosText(glyphArray, count << 1, posPtr, *paint);
- delete[] posPtr;
- }
-
- static void drawTextRun___CIIIIFFZPaintTypeface(
- JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index,
- jint count, jint contextIndex, jint contextCount,
- jfloat x, jfloat y, jboolean isRtl, jlong paintHandle, jlong typefaceHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
-
- int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
- jchar* chars = env->GetCharArrayElements(text, NULL);
- drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex,
- count, contextCount, x, y, bidiFlags, paint, typeface);
- env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
- }
-
- static void drawTextRun__StringIIIIFFZPaintTypeface(
- JNIEnv* env, jobject obj, jlong canvasHandle, jstring text, jint start,
- jint end, jint contextStart, jint contextEnd,
- jfloat x, jfloat y, jboolean isRtl, jlong paintHandle, jlong typefaceHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
-
- int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
- jint count = end - start;
- jint contextCount = contextEnd - contextStart;
- const jchar* chars = env->GetStringChars(text, NULL);
- drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart,
- count, contextCount, x, y, bidiFlags, paint, typeface);
- env->ReleaseStringChars(text, chars);
- }
-
- static void drawPosText___CII_FPaint(JNIEnv* env, jobject, jlong canvasHandle,
- jcharArray text, jint index, jint count,
- jfloatArray pos, jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- jchar* textArray = text ? env->GetCharArrayElements(text, NULL) : NULL;
- jsize textCount = text ? env->GetArrayLength(text) : NULL;
- float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
- int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
- SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
- int indx;
- for (indx = 0; indx < posCount; indx++) {
- posPtr[indx].fX = posArray[indx << 1];
- posPtr[indx].fY = posArray[(indx << 1) + 1];
- }
-
- SkPaint::TextEncoding encoding = paint->getTextEncoding();
- paint->setTextEncoding(SkPaint::kUTF16_TextEncoding);
- canvas->drawPosText(textArray + index, count << 1, posPtr, *paint);
- paint->setTextEncoding(encoding);
-
- if (text) {
- env->ReleaseCharArrayElements(text, textArray, 0);
- }
- if (pos) {
- env->ReleaseFloatArrayElements(pos, posArray, 0);
- }
- delete[] posPtr;
- }
-
- static void drawPosText__String_FPaint(JNIEnv* env, jobject,
- jlong canvasHandle, jstring text,
- jfloatArray pos,
- jlong paintHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- const void* text_ = text ? env->GetStringChars(text, NULL) : NULL;
- int byteLength = text ? env->GetStringLength(text) : 0;
- float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
- int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
- SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
-
- for (int indx = 0; indx < posCount; indx++) {
- posPtr[indx].fX = posArray[indx << 1];
- posPtr[indx].fY = posArray[(indx << 1) + 1];
- }
-
- SkPaint::TextEncoding encoding = paint->getTextEncoding();
- paint->setTextEncoding(SkPaint::kUTF16_TextEncoding);
- canvas->drawPosText(text_, byteLength << 1, posPtr, *paint);
- paint->setTextEncoding(encoding);
-
- if (text) {
- env->ReleaseStringChars(text, (const jchar*) text_);
- }
- if (pos) {
- env->ReleaseFloatArrayElements(pos, posArray, 0);
- }
- delete[] posPtr;
- }
-
- class DrawTextOnPathFunctor {
- public:
- DrawTextOnPathFunctor(const Layout& layout, SkCanvas* canvas, float hOffset,
- float vOffset, SkPaint* paint, SkPath* path)
- : layout(layout), canvas(canvas), hOffset(hOffset), vOffset(vOffset),
- paint(paint), path(path) {
- }
- void operator()(size_t start, size_t end) {
- uint16_t glyphs[1];
- for (size_t i = start; i < end; i++) {
- glyphs[0] = layout.getGlyphId(i);
- float x = hOffset + layout.getX(i);
- float y = vOffset + layout.getY(i);
- canvas->drawTextOnPathHV(glyphs, sizeof(glyphs), *path, x, y, *paint);
- }
- }
- private:
- const Layout& layout;
- SkCanvas* canvas;
- float hOffset;
- float vOffset;
- SkPaint* paint;
- SkPath* path;
- };
-
- static void doDrawTextOnPath(SkPaint* paint, const jchar* text, int count, int bidiFlags,
- float hOffset, float vOffset, SkPath* path, SkCanvas* canvas, TypefaceImpl* typeface) {
- Layout layout;
- std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
- layout.doLayout(text, 0, count, count, css);
- hOffset += MinikinUtils::hOffsetForTextAlign(paint, layout, *path);
- // Set align to left for drawing, as we don't want individual
- // glyphs centered or right-aligned; the offset above takes
- // care of all alignment.
- SkPaint::Align align = paint->getTextAlign();
- paint->setTextAlign(SkPaint::kLeft_Align);
-
- DrawTextOnPathFunctor f(layout, canvas, hOffset, vOffset, paint, path);
- MinikinUtils::forFontRun(layout, paint, f);
- paint->setTextAlign(align);
- }
-
- static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
- jlong canvasHandle, jcharArray text, jint index, jint count,
- jlong pathHandle, jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle,
- jlong typefaceHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
-
- jchar* textArray = env->GetCharArrayElements(text, NULL);
- doDrawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset,
- path, canvas, typeface);
- env->ReleaseCharArrayElements(text, textArray, 0);
- }
-
- static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject,
- jlong canvasHandle, jstring text, jlong pathHandle,
- jfloat hOffset, jfloat vOffset, jint bidiFlags, jlong paintHandle,
- jlong typefaceHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
-
- const jchar* text_ = env->GetStringChars(text, NULL);
- int count = env->GetStringLength(text);
- doDrawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
- path, canvas, typeface);
- env->ReleaseStringChars(text, text_);
- }
-
-
- // This function is a mirror of SkCanvas::getClipBounds except that it does
- // not outset the edge of the clip to account for anti-aliasing. There is
- // a skia bug to investigate pushing this logic into back into skia.
- // (see https://code.google.com/p/skia/issues/detail?id=1303)
- static bool getHardClipBounds(SkCanvas* canvas, SkRect* bounds) {
- SkIRect ibounds;
- if (!canvas->getClipDeviceBounds(&ibounds)) {
- return false;
- }
-
- SkMatrix inverse;
- // if we can't invert the CTM, we can't return local clip bounds
- if (!canvas->getTotalMatrix().invert(&inverse)) {
- if (bounds) {
- bounds->setEmpty();
- }
- return false;
- }
-
- if (NULL != bounds) {
- SkRect r = SkRect::Make(ibounds);
- inverse.mapRect(bounds, r);
- }
- return true;
- }
-
- static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle,
- jobject bounds) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkRect r;
- SkIRect ir;
- bool result = getHardClipBounds(canvas, &r);
-
- if (!result) {
- r.setEmpty();
- }
- r.round(&ir);
-
- (void)GraphicsJNI::irect_to_jrect(ir, env, bounds);
- return result ? JNI_TRUE : JNI_FALSE;
- }
-
- static void getCTM(JNIEnv* env, jobject, jlong canvasHandle,
- jlong matrixHandle) {
- SkCanvas* canvas = getNativeCanvas(canvasHandle);
- SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
- *matrix = canvas->getTotalMatrix();
- }
-};
-
-static JNINativeMethod gCanvasMethods[] = {
- {"finalizer", "(J)V", (void*) SkCanvasGlue::finalizer},
- {"initRaster", "(J)J", (void*) SkCanvasGlue::initRaster},
- {"initCanvas", "(J)J", (void*) SkCanvasGlue::initCanvas},
- {"native_setBitmap", "(JJZ)V", (void*) SkCanvasGlue::setBitmap},
- {"native_isOpaque","(J)Z", (void*) SkCanvasGlue::isOpaque},
- {"native_getWidth","(J)I", (void*) SkCanvasGlue::getWidth},
- {"native_getHeight","(J)I", (void*) SkCanvasGlue::getHeight},
- {"native_save","(JI)I", (void*) SkCanvasGlue::save},
- {"native_saveLayer","(JFFFFJI)I", (void*) SkCanvasGlue::saveLayer},
- {"native_saveLayerAlpha","(JFFFFII)I", (void*) SkCanvasGlue::saveLayerAlpha},
- {"native_restore","(J)V", (void*) SkCanvasGlue::restore},
- {"native_getSaveCount","(J)I", (void*) SkCanvasGlue::getSaveCount},
- {"native_restoreToCount","(JI)V", (void*) SkCanvasGlue::restoreToCount},
- {"native_translate","(JFF)V", (void*) SkCanvasGlue::translate},
- {"native_scale","(JFF)V", (void*) SkCanvasGlue::scale__FF},
- {"native_rotate","(JF)V", (void*) SkCanvasGlue::rotate__F},
- {"native_skew","(JFF)V", (void*) SkCanvasGlue::skew__FF},
- {"native_concat","(JJ)V", (void*) SkCanvasGlue::concat},
- {"native_setMatrix","(JJ)V", (void*) SkCanvasGlue::setMatrix},
- {"native_clipRect","(JFFFFI)Z", (void*) SkCanvasGlue::clipRect},
- {"native_clipPath","(JJI)Z", (void*) SkCanvasGlue::clipPath},
- {"native_clipRegion","(JJI)Z", (void*) SkCanvasGlue::clipRegion},
- {"nativeSetDrawFilter", "(JJ)V", (void*) SkCanvasGlue::setDrawFilter},
- {"native_getClipBounds","(JLandroid/graphics/Rect;)Z",
- (void*) SkCanvasGlue::getClipBounds},
- {"native_getCTM", "(JJ)V", (void*)SkCanvasGlue::getCTM},
- {"native_quickReject","(JJ)Z", (void*) SkCanvasGlue::quickReject__Path},
- {"native_quickReject","(JFFFF)Z", (void*)SkCanvasGlue::quickReject__FFFF},
- {"native_drawRGB","(JIII)V", (void*) SkCanvasGlue::drawRGB},
- {"native_drawARGB","(JIIII)V", (void*) SkCanvasGlue::drawARGB},
- {"native_drawColor","(JI)V", (void*) SkCanvasGlue::drawColor__I},
- {"native_drawColor","(JII)V", (void*) SkCanvasGlue::drawColor__II},
- {"native_drawPaint","(JJ)V", (void*) SkCanvasGlue::drawPaint},
- {"native_drawPoint", "(JFFJ)V", (void*) SkCanvasGlue::drawPoint},
- {"native_drawPoints", "(J[FIIJ)V", (void*) SkCanvasGlue::drawPoints},
- {"native_drawLines", "(J[FIIJ)V", (void*) SkCanvasGlue::drawLines},
- {"native_drawLine","(JFFFFJ)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
- {"native_drawRect","(JFFFFJ)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
- {"native_drawOval","(JFFFFJ)V", (void*) SkCanvasGlue::drawOval},
- {"native_drawCircle","(JFFFJ)V", (void*) SkCanvasGlue::drawCircle},
- {"native_drawArc","(JFFFFFFZJ)V", (void*) SkCanvasGlue::drawArc},
- {"native_drawRoundRect","(JFFFFFFJ)V",
- (void*) SkCanvasGlue::drawRoundRect},
- {"native_drawPath","(JJJ)V", (void*) SkCanvasGlue::drawPath},
- {"native_drawBitmap","(JJFFJIII)V",
- (void*) SkCanvasGlue::drawBitmap__BitmapFFPaint},
- {"native_drawBitmap","(JJLandroid/graphics/Rect;Landroid/graphics/RectF;JII)V",
- (void*) SkCanvasGlue::drawBitmapRF},
- {"native_drawBitmap","(JJLandroid/graphics/Rect;Landroid/graphics/Rect;JII)V",
- (void*) SkCanvasGlue::drawBitmapRR},
- {"native_drawBitmap", "(J[IIIFFIIZJ)V",
- (void*)SkCanvasGlue::drawBitmapArray},
- {"nativeDrawBitmapMatrix", "(JJJJ)V",
- (void*)SkCanvasGlue::drawBitmapMatrix},
- {"nativeDrawBitmapMesh", "(JJII[FI[IIJ)V",
- (void*)SkCanvasGlue::drawBitmapMesh},
- {"nativeDrawVertices", "(JII[FI[FI[II[SIIJ)V",
- (void*)SkCanvasGlue::drawVertices},
- {"native_drawText","(J[CIIFFIJJ)V",
- (void*) SkCanvasGlue::drawText___CIIFFIPaintTypeface},
- {"native_drawText","(JLjava/lang/String;IIFFIJJ)V",
- (void*) SkCanvasGlue::drawText__StringIIFFIPaintTypeface},
- {"native_drawTextRun","(J[CIIIIFFZJJ)V",
- (void*) SkCanvasGlue::drawTextRun___CIIIIFFZPaintTypeface},
- {"native_drawTextRun","(JLjava/lang/String;IIIIFFZJJ)V",
- (void*) SkCanvasGlue::drawTextRun__StringIIIIFFZPaintTypeface},
- {"native_drawTextOnPath","(J[CIIJFFIJJ)V",
- (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint},
- {"native_drawTextOnPath","(JLjava/lang/String;JFFIJJ)V",
- (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
-
- {"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches},
-
- {"freeTextLayoutCaches", "()V", (void*) SkCanvasGlue::freeTextLayoutCaches}
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-#include <android_runtime/AndroidRuntime.h>
-
-#define REG(env, name, array) \
- result = android::AndroidRuntime::registerNativeMethods(env, name, array, \
- SK_ARRAY_COUNT(array)); \
- if (result < 0) return result
-
-int register_android_graphics_Canvas(JNIEnv* env) {
- int result;
-
- REG(env, "android/graphics/Canvas", gCanvasMethods);
-
- return result;
-}
-
-} // namespace android
-
-// GraphicsJNI helper for external clients.
-// We keep the implementation here to avoid exposing NativeCanvasWrapper
-// externally.
-SkCanvas* GraphicsJNI::getNativeCanvas(jlong nativeHandle) {
- return android::SkCanvasGlue::getNativeCanvas(nativeHandle);
-}
diff --git a/core/jni/android/graphics/Canvas.h b/core/jni/android/graphics/Canvas.h
new file mode 100644
index 0000000..710845d
--- /dev/null
+++ b/core/jni/android/graphics/Canvas.h
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_GRAPHICS_CANVAS_H
+#define ANDROID_GRAPHICS_CANVAS_H
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkMatrix.h"
+
+namespace android {
+
+// TODO: move this further up the stack so that all interaction with minikin
+// happens prior to calling into this interface
+class TypefaceImpl;
+
+class Canvas {
+public:
+ virtual ~Canvas() {};
+
+ static Canvas* create_canvas(SkBitmap* bitmap);
+ 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.
+ virtual SkCanvas* getSkCanvas() = 0;
+
+ virtual void setBitmap(SkBitmap* bitmap, bool copyState) = 0;
+
+ virtual bool isOpaque() = 0;
+ virtual int width() = 0;
+ virtual int height() = 0;
+
+// ----------------------------------------------------------------------------
+// Canvas state operations
+// ----------------------------------------------------------------------------
+ // Save (layer)
+ virtual int getSaveCount() const = 0;
+ virtual int save(SkCanvas::SaveFlags flags) = 0;
+ virtual void restore() = 0;
+ virtual void restoreToCount(int saveCount) = 0;
+
+ virtual int saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, SkCanvas::SaveFlags flags) = 0;
+ virtual int saveLayerAlpha(float left, float top, float right, float bottom,
+ int alpha, SkCanvas::SaveFlags flags) = 0;
+
+ // Matrix
+ virtual void getMatrix(SkMatrix* outMatrix) const = 0;
+ virtual void setMatrix(const SkMatrix& matrix) = 0;
+
+ virtual void concat(const SkMatrix& matrix) = 0;
+ virtual void rotate(float degrees) = 0;
+ virtual void scale(float sx, float sy) = 0;
+ virtual void skew(float sx, float sy) = 0;
+ virtual void translate(float dx, float dy) = 0;
+
+ // clip
+ virtual bool getClipBounds(SkRect* outRect) const = 0;
+ virtual bool quickRejectRect(float left, float top, float right, float bottom) const = 0;
+ virtual bool quickRejectPath(const SkPath& path) const = 0;
+
+ virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) = 0;
+ virtual bool clipPath(const SkPath* path, SkRegion::Op op) = 0;
+ virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) = 0;
+
+ // filters
+ virtual void setDrawFilter(SkDrawFilter* drawFilter) = 0;
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations
+// ----------------------------------------------------------------------------
+ virtual void drawColor(int color, SkXfermode::Mode mode) = 0;
+ virtual void drawPaint(const SkPaint& paint) = 0;
+
+ // Geometry
+ virtual void drawPoint(float x, float y, const SkPaint& paint) = 0;
+ virtual void drawPoints(const float* points, int count, const SkPaint& paint) = 0;
+ virtual void drawLine(float startX, float startY, float stopX, float stopY,
+ const SkPaint& paint) = 0;
+ virtual void drawLines(const float* points, int count, const SkPaint& paint) = 0;
+ virtual void drawRect(float left, float top, float right, float bottom,
+ const SkPaint& paint) = 0;
+ virtual void drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, const SkPaint& paint) = 0;
+ virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) = 0;
+ virtual void drawOval(float left, float top, float right, float bottom,
+ const SkPaint& paint) = 0;
+ virtual void drawArc(float left, float top, float right, float bottom,
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) = 0;
+ virtual void drawPath(const SkPath& path, const SkPaint& paint) = 0;
+ virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
+ const float* verts, const float* tex, const int* colors,
+ const uint16_t* indices, int indexCount, const SkPaint& paint) = 0;
+
+ // Bitmap-based
+ virtual void drawBitmap(const SkBitmap& bitmap, float left, float top,
+ const SkPaint* paint) = 0;
+ virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
+ const SkPaint* paint) = 0;
+ virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint) = 0;
+ virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint) = 0;
+
+ // Text
+ virtual void drawText(const char* text, int start, int count, int contextCount,
+ float x, float y, int bidiFlags, const SkPaint& paint,
+ TypefaceImpl* typeface) = 0;
+ virtual void drawPosText(const char* text, const float* positions, int count, int posCount,
+ const SkPaint& paint) = 0;
+ virtual void drawTextOnPath(const char* text, int count, const SkPath& path,
+ float hOffset, float vOffset, const SkPaint& paint) = 0;
+};
+
+}; // namespace android
+#endif // ANDROID_GRAPHICS_CANVAS_H
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 9177696..74be577 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -4,6 +4,7 @@
#include "JNIHelp.h"
#include "GraphicsJNI.h"
+#include "Canvas.h"
#include "SkCanvas.h"
#include "SkDevice.h"
#include "SkMath.h"
@@ -364,7 +365,7 @@
SkASSERT(canvas);
SkASSERT(env->IsInstanceOf(canvas, gCanvas_class));
jlong canvasHandle = env->GetLongField(canvas, gCanvas_nativeInstanceID);
- SkCanvas* c = getNativeCanvas(canvasHandle);
+ SkCanvas* c = reinterpret_cast<android::Canvas*>(canvasHandle)->getSkCanvas();
SkASSERT(c);
return c;
}
@@ -415,7 +416,7 @@
}
jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
- int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density)
+ int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density)
{
SkASSERT(bitmap);
SkASSERT(bitmap->pixelRef());
@@ -429,17 +430,11 @@
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
reinterpret_cast<jlong>(bitmap), buffer,
bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
- ninepatch, layoutbounds);
+ ninePatchChunk, ninePatchInsets);
hasException(env); // For the side effect of logging.
return obj;
}
-jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
- jbyteArray ninepatch, int density)
-{
- return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninepatch, NULL, density);
-}
-
void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap,
bool isPremultiplied)
{
@@ -720,7 +715,7 @@
gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "J");
- gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[B[I)V");
+ gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V");
gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "(IIZ)V");
gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I");
gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index a03391d..28a6edb 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -47,7 +47,6 @@
static SkPoint* jpointf_to_point(JNIEnv*, jobject jpointf, SkPoint* point);
static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf);
- static SkCanvas* getNativeCanvas(jlong nativeHandle);
static SkCanvas* getNativeCanvas(JNIEnv*, jobject canvas);
static SkPaint* getNativePaint(JNIEnv*, jobject paint);
static android::TypefaceImpl* getNativeTypeface(JNIEnv*, jobject paint);
@@ -76,10 +75,12 @@
bitmap's SkAlphaType must already be in sync with bitmapCreateFlags.
*/
static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
- int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density = -1);
+ int bitmapCreateFlags, jbyteArray ninePatch, jobject ninePatchInsets, int density = -1);
static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
- jbyteArray ninepatch, int density = -1);
+ jbyteArray ninePatch, int density = -1) {
+ return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninePatch, NULL, density);
+ }
/** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
sync with isPremultiplied
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index ab5bdb0..e82e8a6 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -23,6 +23,7 @@
#include <Caches.h>
+#include "Canvas.h"
#include "SkCanvas.h"
#include "SkRegion.h"
#include "GraphicsJNI.h"
@@ -119,7 +120,7 @@
static void drawF(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRectF,
jlong bitmapHandle, jlong chunkHandle, jlong paintHandle,
jint destDensity, jint srcDensity) {
- SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
+ SkCanvas* canvas = reinterpret_cast<Canvas*>(canvasHandle)->getSkCanvas();
const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle);
const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
@@ -138,7 +139,7 @@
static void drawI(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRect,
jlong bitmapHandle, jlong chunkHandle, jlong paintHandle,
jint destDensity, jint srcDensity) {
- SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
+ SkCanvas* canvas = reinterpret_cast<Canvas*>(canvasHandle)->getSkCanvas();
const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle);
const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 5daa1ad..ea5193b 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -21,7 +21,7 @@
using namespace android;
bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) {
- if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
+ if (!strcmp("npTc", tag) && length >= sizeof(Res_png_9patch)) {
Res_png_9patch* patch = (Res_png_9patch*) data;
size_t patchSize = patch->serializedSize();
assert(length == patchSize);
@@ -30,12 +30,9 @@
memcpy(patchNew, patch, patchSize);
Res_png_9patch::deserialize(patchNew);
patchNew->fileToDevice();
- free(fPatch);
- fPatch = patchNew;
- fPatchSize = patchSize;
- //printf("9patch: (%d,%d)-(%d,%d)\n",
- // fPatch.sizeLeft, fPatch.sizeTop,
- // fPatch.sizeRight, fPatch.sizeBottom);
+ free(mPatch);
+ mPatch = patchNew;
+ mPatchSize = patchSize;
// now update our host to force index or 32bit config
// 'cause we don't want 565 predithered, since as a 9patch, we know
@@ -47,10 +44,15 @@
table.fPrefFor_8bpc_NoAlpha_src = SkBitmap::kARGB_8888_Config;
table.fPrefFor_8bpc_YesAlpha_src = SkBitmap::kARGB_8888_Config;
- fHost->setPrefConfigTable(table);
- } else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) {
- fLayoutBounds = new int[4];
- memcpy(fLayoutBounds, data, sizeof(int) * 4);
+ mHost->setPrefConfigTable(table);
+ } else if (!strcmp("npLb", tag) && length == sizeof(int32_t) * 4) {
+ mHasInsets = true;
+ memcpy(&mOpticalInsets, data, sizeof(int32_t) * 4);
+ } else if (!strcmp("npOl", tag) && length == 24) { // 4 int32_ts, 1 float, 1 int32_t sized bool
+ mHasInsets = true;
+ memcpy(&mOutlineInsets, data, sizeof(int32_t) * 4);
+ mOutlineRadius = ((const float*)data)[4];
+ mOutlineFilled = ((const int32_t*)data)[5] & 0x01;
}
return true; // keep on decoding
}
diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h
index 2043862..8d3e6cf 100644
--- a/core/jni/android/graphics/NinePatchPeeker.h
+++ b/core/jni/android/graphics/NinePatchPeeker.h
@@ -23,26 +23,34 @@
using namespace android;
class NinePatchPeeker : public SkImageDecoder::Peeker {
- SkImageDecoder* fHost;
+private:
+ // the host lives longer than we do, so a raw ptr is safe
+ SkImageDecoder* mHost;
public:
- NinePatchPeeker(SkImageDecoder* host) {
- // the host lives longer than we do, so a raw ptr is safe
- fHost = host;
- fPatch = NULL;
- fPatchSize = 0;
- fLayoutBounds = NULL;
+ NinePatchPeeker(SkImageDecoder* host)
+ : mHost(host)
+ , mPatch(NULL)
+ , mPatchSize(0)
+ , mHasInsets(false)
+ , mOutlineRadius(0)
+ , mOutlineFilled(false) {
+ memset(mOpticalInsets, 0, 4 * sizeof(int32_t));
+ memset(mOutlineInsets, 0, 4 * sizeof(int32_t));
}
~NinePatchPeeker() {
- free(fPatch);
- delete fLayoutBounds;
+ free(mPatch);
}
- Res_png_9patch* fPatch;
- size_t fPatchSize;
- int *fLayoutBounds;
-
virtual bool peek(const char tag[], const void* data, size_t length);
+
+ Res_png_9patch* mPatch;
+ size_t mPatchSize;
+ bool mHasInsets;
+ int32_t mOpticalInsets[4];
+ int32_t mOutlineInsets[4];
+ float mOutlineRadius;
+ bool mOutlineFilled;
};
#endif // NinePatchPeeker_h
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index bc0c25f..d214575 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
+#include "Canvas.h"
#include "Picture.h"
-#include "SkCanvas.h"
#include "SkStream.h"
namespace android {
@@ -36,12 +36,13 @@
}
}
-SkCanvas* Picture::beginRecording(int width, int height) {
+Canvas* Picture::beginRecording(int width, int height) {
mPicture.reset(NULL);
mRecorder.reset(new SkPictureRecorder);
mWidth = width;
mHeight = height;
- return mRecorder->beginRecording(width, height, NULL, 0);
+ SkCanvas* canvas = mRecorder->beginRecording(width, height, NULL, 0);
+ return Canvas::create_canvas(canvas);
}
void Picture::endRecording() {
@@ -93,14 +94,14 @@
}
}
-void Picture::draw(SkCanvas* canvas) {
+void Picture::draw(Canvas* canvas) {
if (NULL != mRecorder.get()) {
this->endRecording();
SkASSERT(NULL != mPicture.get());
}
if (NULL != mPicture.get()) {
// TODO: remove this const_cast once pictures are immutable
- const_cast<SkPicture*>(mPicture.get())->draw(canvas);
+ const_cast<SkPicture*>(mPicture.get())->draw(canvas->getSkCanvas());
}
}
diff --git a/core/jni/android/graphics/Picture.h b/core/jni/android/graphics/Picture.h
index abb0403..a2e5d4a 100644
--- a/core/jni/android/graphics/Picture.h
+++ b/core/jni/android/graphics/Picture.h
@@ -22,14 +22,13 @@
#include "SkRefCnt.h"
#include "SkTemplates.h"
-class SkCanvas;
-class SkPicture;
-class SkPictureRecorder;
class SkStream;
class SkWStream;
namespace android {
+class Canvas;
+
// Skia's SkPicture class has been split into an SkPictureRecorder
// and an SkPicture. AndroidPicture recreates the functionality
// of the old SkPicture interface by flip-flopping between the two
@@ -38,7 +37,7 @@
public:
explicit Picture(const Picture* src = NULL);
- SkCanvas* beginRecording(int width, int height);
+ Canvas* beginRecording(int width, int height);
void endRecording();
@@ -50,7 +49,7 @@
void serialize(SkWStream* stream) const;
- void draw(SkCanvas* canvas);
+ void draw(Canvas* canvas);
private:
int mWidth;
diff --git a/core/jni/android/graphics/SkiaCanvas.cpp b/core/jni/android/graphics/SkiaCanvas.cpp
new file mode 100644
index 0000000..5e93313
--- /dev/null
+++ b/core/jni/android/graphics/SkiaCanvas.cpp
@@ -0,0 +1,773 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+#include "Canvas.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkCanvas.h"
+#include "SkClipStack.h"
+#include "SkDevice.h"
+#include "SkDeque.h"
+#include "SkDrawFilter.h"
+#include "SkGraphics.h"
+#include "SkPorterDuff.h"
+#include "SkShader.h"
+#include "SkTArray.h"
+#include "SkTemplates.h"
+
+#include <minikin/Layout.h>
+#include "MinikinSkia.h"
+#include "MinikinUtils.h"
+
+#include "TypefaceImpl.h"
+
+#include "unicode/ubidi.h"
+#include "unicode/ushape.h"
+
+#include <utils/Log.h>
+
+namespace android {
+
+// Holds an SkCanvas reference plus additional native data.
+class SkiaCanvas : public Canvas {
+public:
+ SkiaCanvas(SkBitmap* bitmap);
+
+ SkiaCanvas(SkCanvas* canvas) : mCanvas(canvas) {
+ SkASSERT(canvas);
+ }
+
+ virtual SkCanvas* getSkCanvas() {
+ return mCanvas.get();
+ }
+
+ virtual void setBitmap(SkBitmap* bitmap, bool copyState);
+
+ virtual bool isOpaque();
+ virtual int width();
+ virtual int height();
+
+ virtual int getSaveCount() const;
+ virtual int save(SkCanvas::SaveFlags flags);
+ virtual void restore();
+ virtual void restoreToCount(int saveCount);
+
+ virtual int saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, SkCanvas::SaveFlags flags);
+ virtual int saveLayerAlpha(float left, float top, float right, float bottom,
+ int alpha, SkCanvas::SaveFlags flags);
+
+ virtual void getMatrix(SkMatrix* outMatrix) const;
+ virtual void setMatrix(const SkMatrix& matrix);
+ virtual void concat(const SkMatrix& matrix);
+ virtual void rotate(float degrees);
+ virtual void scale(float sx, float sy);
+ virtual void skew(float sx, float sy);
+ virtual void translate(float dx, float dy);
+
+ virtual bool getClipBounds(SkRect* outRect) const;
+ virtual bool quickRejectRect(float left, float top, float right, float bottom) const;
+ virtual bool quickRejectPath(const SkPath& path) const;
+ virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+ virtual bool clipPath(const SkPath* path, SkRegion::Op op);
+ virtual bool clipRegion(const SkRegion* region, SkRegion::Op op);
+
+ virtual void setDrawFilter(SkDrawFilter* drawFilter);
+
+ virtual void drawColor(int color, SkXfermode::Mode mode);
+ virtual void drawPaint(const SkPaint& paint);
+
+ virtual void drawPoint(float x, float y, const SkPaint& paint);
+ virtual void drawPoints(const float* points, int count, const SkPaint& paint);
+ virtual void drawLine(float startX, float startY, float stopX, float stopY,
+ const SkPaint& paint);
+ virtual void drawLines(const float* points, int count, const SkPaint& paint);
+ virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint);
+ virtual void drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, const SkPaint& paint);
+ virtual void drawCircle(float x, float y, float radius, const SkPaint& paint);
+ virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint);
+ virtual void drawArc(float left, float top, float right, float bottom,
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint);
+ virtual void drawPath(const SkPath& path, const SkPaint& paint);
+ virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
+ const float* verts, const float* tex, const int* colors,
+ const uint16_t* indices, int indexCount, const SkPaint& paint);
+
+ virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint);
+ virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint);
+ virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint);
+ virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint);
+
+ virtual void drawText(const char* text, int start, int count, int contextCount,
+ float x, float y, int bidiFlags, const SkPaint& paint, TypefaceImpl* typeface);
+ virtual void drawPosText(const char* text, const float* positions, int count, int posCount,
+ const SkPaint& paint);
+ virtual void drawTextOnPath(const char* text, int count, const SkPath& path,
+ float hOffset, float vOffset, const SkPaint& paint);
+
+private:
+ struct SaveRec {
+ int saveCount;
+ SkCanvas::SaveFlags saveFlags;
+ };
+
+ void recordPartialSave(SkCanvas::SaveFlags flags);
+ void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount);
+ void applyClips(const SkTArray<SkClipStack::Element>& clips);
+
+ void drawPoints(const float* points, int count, const SkPaint& paint,
+ SkCanvas::PointMode mode);
+ void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
+
+ SkAutoTUnref<SkCanvas> mCanvas;
+ SkAutoTDelete<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
+};
+
+// Construct an SkCanvas from the bitmap.
+static SkCanvas* createCanvas(SkBitmap* bitmap) {
+ if (bitmap) {
+ return SkNEW_ARGS(SkCanvas, (*bitmap));
+ }
+
+ // Create an empty bitmap device to prevent callers from crashing
+ // if they attempt to draw into this canvas.
+ SkBitmap emptyBitmap;
+ return new SkCanvas(emptyBitmap);
+}
+
+Canvas* Canvas::create_canvas(SkBitmap* bitmap) {
+ return new SkiaCanvas(bitmap);
+}
+
+Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas) {
+ return new SkiaCanvas(skiaCanvas);
+}
+
+SkiaCanvas::SkiaCanvas(SkBitmap* bitmap) {
+ mCanvas.reset(createCanvas(bitmap));
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Replace Bitmap
+// ----------------------------------------------------------------------------
+
+class ClipCopier : public SkCanvas::ClipVisitor {
+public:
+ ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {}
+
+ virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) {
+ m_dstCanvas->clipRect(rect, op, antialias);
+ }
+ virtual void clipRRect(const SkRRect& rrect, SkRegion::Op op, bool antialias) {
+ m_dstCanvas->clipRRect(rrect, op, antialias);
+ }
+ virtual void clipPath(const SkPath& path, SkRegion::Op op, bool antialias) {
+ m_dstCanvas->clipPath(path, op, antialias);
+ }
+
+private:
+ SkCanvas* m_dstCanvas;
+};
+
+void SkiaCanvas::setBitmap(SkBitmap* bitmap, bool copyState) {
+ SkCanvas* newCanvas = createCanvas(bitmap);
+ SkASSERT(newCanvas);
+
+ if (copyState) {
+ // Copy the canvas matrix & clip state.
+ newCanvas->setMatrix(mCanvas->getTotalMatrix());
+ if (NULL != mCanvas->getDevice() && NULL != newCanvas->getDevice()) {
+ ClipCopier copier(newCanvas);
+ mCanvas->replayClips(&copier);
+ }
+ }
+
+ // unrefs the existing canvas
+ mCanvas.reset(newCanvas);
+
+ // clean up the old save stack
+ mSaveStack.reset(NULL);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations
+// ----------------------------------------------------------------------------
+
+bool SkiaCanvas::isOpaque() {
+ return mCanvas->getDevice()->accessBitmap(false).isOpaque();
+}
+
+int SkiaCanvas::width() {
+ return mCanvas->getBaseLayerSize().width();
+}
+
+int SkiaCanvas::height() {
+ return mCanvas->getBaseLayerSize().height();
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Save (layer)
+// ----------------------------------------------------------------------------
+
+int SkiaCanvas::getSaveCount() const {
+ return mCanvas->getSaveCount();
+}
+
+int SkiaCanvas::save(SkCanvas::SaveFlags flags) {
+ int count = mCanvas->save();
+ recordPartialSave(flags);
+ return count;
+}
+
+void SkiaCanvas::restore() {
+ const SaveRec* rec = (NULL == mSaveStack.get())
+ ? NULL
+ : static_cast<SaveRec*>(mSaveStack->back());
+ int currentSaveCount = mCanvas->getSaveCount() - 1;
+ SkASSERT(NULL == rec || currentSaveCount >= rec->saveCount);
+
+ if (NULL == rec || rec->saveCount != currentSaveCount) {
+ // Fast path - no record for this frame.
+ mCanvas->restore();
+ return;
+ }
+
+ bool preserveMatrix = !(rec->saveFlags & SkCanvas::kMatrix_SaveFlag);
+ bool preserveClip = !(rec->saveFlags & SkCanvas::kClip_SaveFlag);
+
+ SkMatrix savedMatrix;
+ if (preserveMatrix) {
+ savedMatrix = mCanvas->getTotalMatrix();
+ }
+
+ SkTArray<SkClipStack::Element> savedClips;
+ if (preserveClip) {
+ saveClipsForFrame(savedClips, currentSaveCount);
+ }
+
+ mCanvas->restore();
+
+ if (preserveMatrix) {
+ mCanvas->setMatrix(savedMatrix);
+ }
+
+ if (preserveClip && !savedClips.empty()) {
+ applyClips(savedClips);
+ }
+
+ mSaveStack->pop_back();
+}
+
+void SkiaCanvas::restoreToCount(int restoreCount) {
+ while (mCanvas->getSaveCount() > restoreCount) {
+ this->restore();
+ }
+}
+
+int SkiaCanvas::saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, SkCanvas::SaveFlags flags) {
+ SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
+ int count = mCanvas->saveLayer(&bounds, paint, flags | SkCanvas::kMatrixClip_SaveFlag);
+ recordPartialSave(flags);
+ return count;
+}
+
+int SkiaCanvas::saveLayerAlpha(float left, float top, float right, float bottom,
+ int alpha, SkCanvas::SaveFlags flags) {
+ SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
+ int count = mCanvas->saveLayerAlpha(&bounds, alpha, flags | SkCanvas::kMatrixClip_SaveFlag);
+ recordPartialSave(flags);
+ return count;
+}
+
+// ----------------------------------------------------------------------------
+// functions to emulate legacy SaveFlags (i.e. independent matrix/clip flags)
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::recordPartialSave(SkCanvas::SaveFlags flags) {
+ // A partial save is a save operation which doesn't capture the full canvas state.
+ // (either kMatrix_SaveFlags or kClip_SaveFlag is missing).
+
+ // Mask-out non canvas state bits.
+ flags = static_cast<SkCanvas::SaveFlags>(flags & SkCanvas::kMatrixClip_SaveFlag);
+
+ if (SkCanvas::kMatrixClip_SaveFlag == flags) {
+ // not a partial save.
+ return;
+ }
+
+ if (NULL == mSaveStack.get()) {
+ mSaveStack.reset(SkNEW_ARGS(SkDeque, (sizeof(struct SaveRec), 8)));
+ }
+
+ SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back());
+ // Store the save counter in the SkClipStack domain.
+ // (0-based, equal to the number of save ops on the stack).
+ rec->saveCount = mCanvas->getSaveCount() - 1;
+ rec->saveFlags = flags;
+}
+
+void SkiaCanvas::saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount) {
+ SkClipStack::Iter clipIterator(*mCanvas->getClipStack(),
+ SkClipStack::Iter::kTop_IterStart);
+ while (const SkClipStack::Element* elem = clipIterator.next()) {
+ if (elem->getSaveCount() < frameSaveCount) {
+ // done with the current frame.
+ break;
+ }
+ SkASSERT(elem->getSaveCount() == frameSaveCount);
+ clips.push_back(*elem);
+ }
+}
+
+void SkiaCanvas::applyClips(const SkTArray<SkClipStack::Element>& clips) {
+ ClipCopier clipCopier(mCanvas);
+
+ // The clip stack stores clips in device space.
+ SkMatrix origMatrix = mCanvas->getTotalMatrix();
+ mCanvas->resetMatrix();
+
+ // We pushed the clips in reverse order.
+ for (int i = clips.count() - 1; i >= 0; --i) {
+ clips[i].replay(&clipCopier);
+ }
+
+ mCanvas->setMatrix(origMatrix);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Matrix
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::getMatrix(SkMatrix* outMatrix) const {
+ *outMatrix = mCanvas->getTotalMatrix();
+}
+
+void SkiaCanvas::setMatrix(const SkMatrix& matrix) {
+ mCanvas->setMatrix(matrix);
+}
+
+void SkiaCanvas::concat(const SkMatrix& matrix) {
+ mCanvas->concat(matrix);
+}
+
+void SkiaCanvas::rotate(float degrees) {
+ mCanvas->rotate(degrees);
+}
+
+void SkiaCanvas::scale(float sx, float sy) {
+ mCanvas->scale(sx, sy);
+}
+
+void SkiaCanvas::skew(float sx, float sy) {
+ mCanvas->skew(sx, sy);
+}
+
+void SkiaCanvas::translate(float dx, float dy) {
+ mCanvas->translate(dx, dy);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Clips
+// ----------------------------------------------------------------------------
+
+// This function is a mirror of SkCanvas::getClipBounds except that it does
+// not outset the edge of the clip to account for anti-aliasing. There is
+// a skia bug to investigate pushing this logic into back into skia.
+// (see https://code.google.com/p/skia/issues/detail?id=1303)
+bool SkiaCanvas::getClipBounds(SkRect* outRect) const {
+ SkIRect ibounds;
+ if (!mCanvas->getClipDeviceBounds(&ibounds)) {
+ return false;
+ }
+
+ SkMatrix inverse;
+ // if we can't invert the CTM, we can't return local clip bounds
+ if (!mCanvas->getTotalMatrix().invert(&inverse)) {
+ if (outRect) {
+ outRect->setEmpty();
+ }
+ return false;
+ }
+
+ if (NULL != outRect) {
+ SkRect r = SkRect::Make(ibounds);
+ inverse.mapRect(outRect, r);
+ }
+ return true;
+}
+
+bool SkiaCanvas::quickRejectRect(float left, float top, float right, float bottom) const {
+ SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
+ return mCanvas->quickReject(bounds);
+}
+
+bool SkiaCanvas::quickRejectPath(const SkPath& path) const {
+ return mCanvas->quickReject(path);
+}
+
+bool SkiaCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
+ SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
+ mCanvas->clipRect(rect, op);
+ return mCanvas->isClipEmpty();
+}
+
+bool SkiaCanvas::clipPath(const SkPath* path, SkRegion::Op op) {
+ mCanvas->clipPath(*path, op);
+ return mCanvas->isClipEmpty();
+}
+
+bool SkiaCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) {
+ SkPath rgnPath;
+ if (region->getBoundaryPath(&rgnPath)) {
+ // The region is specified in device space.
+ SkMatrix savedMatrix = mCanvas->getTotalMatrix();
+ mCanvas->resetMatrix();
+ mCanvas->clipPath(rgnPath, op);
+ mCanvas->setMatrix(savedMatrix);
+ } else {
+ mCanvas->clipRect(SkRect::MakeEmpty(), op);
+ }
+ return mCanvas->isClipEmpty();
+}
+
+// ----------------------------------------------------------------------------
+// Canvas state operations: Filters
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::setDrawFilter(SkDrawFilter* drawFilter) {
+ mCanvas->setDrawFilter(drawFilter);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::drawColor(int color, SkXfermode::Mode mode) {
+ mCanvas->drawColor(color, mode);
+}
+
+void SkiaCanvas::drawPaint(const SkPaint& paint) {
+ mCanvas->drawPaint(paint);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations: Geometry
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint,
+ SkCanvas::PointMode mode) {
+ // convert the floats into SkPoints
+ count >>= 1; // now it is the number of points
+ SkAutoSTMalloc<32, SkPoint> storage(count);
+ SkPoint* pts = storage.get();
+ for (int i = 0; i < count; i++) {
+ pts[i].set(points[0], points[1]);
+ points += 2;
+ }
+ mCanvas->drawPoints(mode, count, pts, paint);
+}
+
+
+void SkiaCanvas::drawPoint(float x, float y, const SkPaint& paint) {
+ mCanvas->drawPoint(x, y, paint);
+}
+
+void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
+ this->drawPoints(points, count, paint, SkCanvas::kPoints_PointMode);
+}
+
+void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY,
+ const SkPaint& paint) {
+ mCanvas->drawLine(startX, startY, stopX, stopY, paint);
+}
+
+void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
+ this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
+}
+
+void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
+ const SkPaint& paint) {
+ mCanvas->drawRectCoords(left, top, right, bottom, paint);
+
+}
+
+void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, const SkPaint& paint) {
+ SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
+ mCanvas->drawRoundRect(rect, rx, ry, paint);
+}
+
+void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
+ mCanvas->drawCircle(x, y, radius, paint);
+}
+
+void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
+ SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
+ mCanvas->drawOval(oval, paint);
+}
+
+void SkiaCanvas::drawArc(float left, float top, float right, float bottom,
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
+ SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
+ mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+}
+
+void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+ mCanvas->drawPath(path, paint);
+}
+
+void SkiaCanvas::drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
+ const float* verts, const float* texs, const int* colors,
+ const uint16_t* indices, int indexCount, const SkPaint& paint) {
+#ifndef SK_SCALAR_IS_FLOAT
+ SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
+#endif
+ const int ptCount = vertexCount >> 1;
+ mCanvas->drawVertices(vertexMode, ptCount, (SkPoint*)verts, (SkPoint*)texs,
+ (SkColor*)colors, NULL, indices, indexCount, paint);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations: Bitmaps
+// ----------------------------------------------------------------------------
+
+void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
+ mCanvas->drawBitmap(bitmap, left, top, paint);
+}
+
+void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) {
+ mCanvas->drawBitmapMatrix(bitmap, matrix, paint);
+}
+
+void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint) {
+ SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
+ SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
+ mCanvas->drawBitmapRectToRect(bitmap, &srcRect, dstRect, paint);
+}
+
+void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint) {
+
+ const int ptCount = (meshWidth + 1) * (meshHeight + 1);
+ const int indexCount = meshWidth * meshHeight * 6;
+
+ /* Our temp storage holds 2 or 3 arrays.
+ texture points [ptCount * sizeof(SkPoint)]
+ optionally vertex points [ptCount * sizeof(SkPoint)] if we need a
+ copy to convert from float to fixed
+ indices [ptCount * sizeof(uint16_t)]
+ */
+ ssize_t storageSize = ptCount * sizeof(SkPoint); // texs[]
+ storageSize += indexCount * sizeof(uint16_t); // indices[]
+
+
+#ifndef SK_SCALAR_IS_FLOAT
+ SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
+#endif
+ SkAutoMalloc storage(storageSize);
+ SkPoint* texs = (SkPoint*)storage.get();
+ uint16_t* indices = (uint16_t*)(texs + ptCount);
+
+ // cons up texture coordinates and indices
+ {
+ const SkScalar w = SkIntToScalar(bitmap.width());
+ const SkScalar h = SkIntToScalar(bitmap.height());
+ const SkScalar dx = w / meshWidth;
+ const SkScalar dy = h / meshHeight;
+
+ SkPoint* texsPtr = texs;
+ SkScalar y = 0;
+ for (int i = 0; i <= meshHeight; i++) {
+ if (i == meshHeight) {
+ y = h; // to ensure numerically we hit h exactly
+ }
+ SkScalar x = 0;
+ for (int j = 0; j < meshWidth; j++) {
+ texsPtr->set(x, y);
+ texsPtr += 1;
+ x += dx;
+ }
+ texsPtr->set(w, y);
+ texsPtr += 1;
+ y += dy;
+ }
+ SkASSERT(texsPtr - texs == ptCount);
+ }
+
+ // cons up indices
+ {
+ uint16_t* indexPtr = indices;
+ int index = 0;
+ for (int i = 0; i < meshHeight; i++) {
+ for (int j = 0; j < meshWidth; j++) {
+ // lower-left triangle
+ *indexPtr++ = index;
+ *indexPtr++ = index + meshWidth + 1;
+ *indexPtr++ = index + meshWidth + 2;
+ // upper-right triangle
+ *indexPtr++ = index;
+ *indexPtr++ = index + meshWidth + 2;
+ *indexPtr++ = index + 1;
+ // bump to the next cell
+ index += 1;
+ }
+ // bump to the next row
+ index += 1;
+ }
+ SkASSERT(indexPtr - indices == indexCount);
+ SkASSERT((char*)indexPtr - (char*)storage.get() == storageSize);
+ }
+
+ // double-check that we have legal indices
+#ifdef SK_DEBUG
+ {
+ for (int i = 0; i < indexCount; i++) {
+ SkASSERT((unsigned)indices[i] < (unsigned)ptCount);
+ }
+ }
+#endif
+
+ // cons-up a shader for the bitmap
+ SkPaint tmpPaint;
+ if (paint) {
+ tmpPaint = *paint;
+ }
+ SkShader* shader = SkShader::CreateBitmapShader(bitmap,
+ SkShader::kClamp_TileMode,
+ SkShader::kClamp_TileMode);
+ SkSafeUnref(tmpPaint.setShader(shader));
+
+ mCanvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, (SkPoint*)vertices,
+ texs, (const SkColor*)colors, NULL, indices,
+ indexCount, tmpPaint);
+}
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations: Text
+// ----------------------------------------------------------------------------
+
+class DrawTextFunctor {
+public:
+ DrawTextFunctor(const Layout& layout, SkCanvas* canvas, float x, float y, SkPaint* paint,
+ uint16_t* glyphs, SkPoint* pos)
+ : layout(layout), canvas(canvas), x(x), y(y), paint(paint), glyphs(glyphs),
+ pos(pos) { }
+
+ void operator()(size_t start, size_t end) {
+ for (size_t i = start; i < end; i++) {
+ glyphs[i] = layout.getGlyphId(i);
+ pos[i].fX = x + layout.getX(i);
+ pos[i].fY = y + layout.getY(i);
+ }
+ canvas->drawPosText(glyphs + start, (end - start) << 1, pos + start, *paint);
+ }
+private:
+ const Layout& layout;
+ SkCanvas* canvas;
+ float x;
+ float y;
+ SkPaint* paint;
+ uint16_t* glyphs;
+ SkPoint* pos;
+};
+
+void SkiaCanvas::drawText(const char* text, int start, int count, int contextCount,
+ float x, float y, int bidiFlags, const SkPaint& paint, TypefaceImpl* typeface) {
+ Layout layout;
+ std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
+ layout.doLayout((uint16_t*)text, start, count, contextCount, css);
+
+ size_t nGlyphs = layout.nGlyphs();
+ uint16_t* glyphs = new uint16_t[nGlyphs];
+ SkPoint* pos = new SkPoint[nGlyphs];
+
+ SkPaint paintCopy(paint);
+ x += MinikinUtils::xOffsetForTextAlign(&paintCopy, layout);
+ paintCopy.setTextAlign(SkPaint::kLeft_Align);
+ paintCopy.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ DrawTextFunctor f(layout, mCanvas, x, y, &paintCopy, glyphs, pos);
+ MinikinUtils::forFontRun(layout, &paintCopy, f);
+ drawTextDecorations(x, y, layout.getAdvance(), paintCopy);
+
+ delete[] glyphs;
+ delete[] pos;
+}
+
+// Same values used by Skia
+#define kStdStrikeThru_Offset (-6.0f / 21.0f)
+#define kStdUnderline_Offset (1.0f / 9.0f)
+#define kStdUnderline_Thickness (1.0f / 18.0f)
+
+void SkiaCanvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+ uint32_t flags;
+ SkDrawFilter* drawFilter = mCanvas->getDrawFilter();
+ if (drawFilter) {
+ SkPaint paintCopy(paint);
+ drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+ flags = paintCopy.getFlags();
+ } else {
+ flags = paint.getFlags();
+ }
+ if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+ SkScalar left = x;
+ SkScalar right = x + length;
+ float textSize = paint.getTextSize();
+ float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
+ if (flags & SkPaint::kUnderlineText_Flag) {
+ SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
+ mCanvas->drawRectCoords(left, top, right, bottom, paint);
+ }
+ if (flags & SkPaint::kStrikeThruText_Flag) {
+ SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
+ SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
+ mCanvas->drawRectCoords(left, top, right, bottom, paint);
+ }
+ }
+}
+
+void SkiaCanvas::drawPosText(const char* text, const float* positions, int count, int posCount,
+ const SkPaint& paint) {
+ SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
+ int indx;
+ for (indx = 0; indx < posCount; indx++) {
+ posPtr[indx].fX = positions[indx << 1];
+ posPtr[indx].fY = positions[(indx << 1) + 1];
+ }
+
+ SkPaint paintCopy(paint);
+ paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+ mCanvas->drawPosText(text, count, posPtr, paintCopy);
+
+ delete[] posPtr;
+}
+
+void SkiaCanvas::drawTextOnPath(const char* text, int count, const SkPath& path,
+ float hOffset, float vOffset, const SkPaint& paint) {
+ mCanvas->drawTextOnPathHV(text, count, path, hOffset, vOffset, paint);
+}
+
+} // namespace android
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index 3812c27..9436a47 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -19,9 +19,9 @@
#include <android_runtime/AndroidRuntime.h>
#include <vector>
+#include "Canvas.h"
#include "CreateJavaOutputStreamAdaptor.h"
-#include "SkCanvas.h"
#include "SkDocument.h"
#include "SkPicture.h"
#include "SkPictureRecorder.h"
@@ -132,8 +132,9 @@
jint pageWidth, jint pageHeight,
jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) {
PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
- return reinterpret_cast<jlong>(document->startPage(pageWidth, pageHeight,
- contentLeft, contentTop, contentRight, contentBottom));
+ SkCanvas* canvas = document->startPage(pageWidth, pageHeight,
+ contentLeft, contentTop, contentRight, contentBottom);
+ return reinterpret_cast<jlong>(Canvas::create_canvas(canvas));
}
static void nativeFinishPage(JNIEnv* env, jobject thiz, jlong documentPtr) {
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
new file mode 100644
index 0000000..fd96a90
--- /dev/null
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -0,0 +1,653 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "Canvas.h"
+#include "SkGraphics.h"
+#include "SkPorterDuff.h"
+#include "TypefaceImpl.h"
+
+#include <minikin/Layout.h>
+#include "MinikinSkia.h"
+#include "MinikinUtils.h"
+
+namespace android {
+
+namespace CanvasJNI {
+
+static Canvas* get_canvas(jlong canvasHandle) {
+ return reinterpret_cast<Canvas*>(canvasHandle);
+}
+
+static void finalizer(JNIEnv* env, jobject clazz, jlong canvasHandle) {
+ delete get_canvas(canvasHandle);
+}
+
+// Native wrapper constructor used by Canvas(Bitmap)
+static jlong initRaster(JNIEnv* env, jobject, jlong bitmapHandle) {
+ SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+ return reinterpret_cast<jlong>(Canvas::create_canvas(bitmap));
+}
+
+// Set the given bitmap as the new draw target (wrapped in a new SkCanvas),
+// optionally copying canvas matrix & clip state.
+static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+ jboolean copyState) {
+ SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+ get_canvas(canvasHandle)->setBitmap(bitmap, copyState);
+}
+
+static jboolean isOpaque(JNIEnv*, jobject, jlong canvasHandle) {
+ return get_canvas(canvasHandle)->isOpaque() ? JNI_TRUE : JNI_FALSE;
+}
+
+static jint getWidth(JNIEnv*, jobject, jlong canvasHandle) {
+ return static_cast<jint>(get_canvas(canvasHandle)->width());
+}
+
+static jint getHeight(JNIEnv*, jobject, jlong canvasHandle) {
+ return static_cast<jint>(get_canvas(canvasHandle)->height());
+}
+
+static jint getSaveCount(JNIEnv*, jobject, jlong canvasHandle) {
+ return static_cast<jint>(get_canvas(canvasHandle)->getSaveCount());
+}
+
+static jint save(JNIEnv*, jobject, jlong canvasHandle, jint flagsHandle) {
+ SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+ return static_cast<jint>(get_canvas(canvasHandle)->save(flags));
+}
+
+static jint saveLayer(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
+ jfloat r, jfloat b, jlong paintHandle, jint flagsHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+ return static_cast<jint>(get_canvas(canvasHandle)->saveLayer(l, t, r, b, paint, flags));
+}
+
+static jint saveLayerAlpha(JNIEnv* env, jobject, jlong canvasHandle, jfloat l, jfloat t,
+ jfloat r, jfloat b, jint alpha, jint flagsHandle) {
+ SkCanvas::SaveFlags flags = static_cast<SkCanvas::SaveFlags>(flagsHandle);
+ return static_cast<jint>(get_canvas(canvasHandle)->saveLayerAlpha(l, t, r, b, alpha, flags));
+}
+
+static void restore(JNIEnv* env, jobject, jlong canvasHandle) {
+ Canvas* canvas = get_canvas(canvasHandle);
+ if (canvas->getSaveCount() <= 1) { // cannot restore anymore
+ doThrowISE(env, "Underflow in restore");
+ return;
+ }
+ canvas->restore();
+}
+
+static void restoreToCount(JNIEnv* env, jobject, jlong canvasHandle, jint restoreCount) {
+ Canvas* canvas = get_canvas(canvasHandle);
+ if (restoreCount < 1 || restoreCount > canvas->getSaveCount()) {
+ doThrowIAE(env, "Underflow in restoreToCount");
+ return;
+ }
+ canvas->restoreToCount(restoreCount);
+}
+
+static void getCTM(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+ SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+ get_canvas(canvasHandle)->getMatrix(matrix);
+}
+
+static void setMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+ const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+ get_canvas(canvasHandle)->setMatrix(matrix ? *matrix : SkMatrix::I());
+}
+
+static void concat(JNIEnv* env, jobject, jlong canvasHandle, jlong matrixHandle) {
+ const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+ get_canvas(canvasHandle)->concat(*matrix);
+}
+
+static void rotate(JNIEnv*, jobject, jlong canvasHandle, jfloat degrees) {
+ get_canvas(canvasHandle)->rotate(degrees);
+}
+
+static void scale(JNIEnv*, jobject, jlong canvasHandle, jfloat sx, jfloat sy) {
+ get_canvas(canvasHandle)->scale(sx, sy);
+}
+
+static void skew(JNIEnv*, jobject, jlong canvasHandle, jfloat sx, jfloat sy) {
+ get_canvas(canvasHandle)->skew(sx, sy);
+}
+
+static void translate(JNIEnv*, jobject, jlong canvasHandle, jfloat dx, jfloat dy) {
+ get_canvas(canvasHandle)->translate(dx, dy);
+}
+
+static jboolean getClipBounds(JNIEnv* env, jobject, jlong canvasHandle, jobject bounds) {
+ SkRect r;
+ SkIRect ir;
+ bool result = get_canvas(canvasHandle)->getClipBounds(&r);
+
+ if (!result) {
+ r.setEmpty();
+ }
+ r.round(&ir);
+
+ (void)GraphicsJNI::irect_to_jrect(ir, env, bounds);
+ return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean quickRejectRect(JNIEnv* env, jobject, jlong canvasHandle,
+ jfloat left, jfloat top, jfloat right, jfloat bottom) {
+ bool result = get_canvas(canvasHandle)->quickRejectRect(left, top, right, bottom);
+ return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean quickRejectPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle) {
+ SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+ bool result = get_canvas(canvasHandle)->quickRejectPath(*path);
+ return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean clipRect(JNIEnv*, jobject, jlong canvasHandle, jfloat l, jfloat t,
+ jfloat r, jfloat b, jint opHandle) {
+ SkRegion::Op op = static_cast<SkRegion::Op>(opHandle);
+ bool emptyClip = get_canvas(canvasHandle)->clipRect(l, t, r, b, op);
+ return emptyClip ? JNI_FALSE : JNI_TRUE;
+}
+
+static jboolean clipPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
+ jint opHandle) {
+ SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+ SkRegion::Op op = static_cast<SkRegion::Op>(opHandle);
+ bool emptyClip = get_canvas(canvasHandle)->clipPath(path, op);
+ return emptyClip ? JNI_FALSE : JNI_TRUE;
+}
+
+static jboolean clipRegion(JNIEnv* env, jobject, jlong canvasHandle, jlong deviceRgnHandle,
+ jint opHandle) {
+ SkRegion* deviceRgn = reinterpret_cast<SkRegion*>(deviceRgnHandle);
+ SkRegion::Op op = static_cast<SkRegion::Op>(opHandle);
+ bool emptyClip = get_canvas(canvasHandle)->clipRegion(deviceRgn, op);
+ return emptyClip ? JNI_FALSE : JNI_TRUE;
+}
+
+static void drawColor(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) {
+ SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle);
+ get_canvas(canvasHandle)->drawColor(color, SkPorterDuff::ToXfermodeMode(mode));
+}
+
+static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle, jlong paintHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawPaint(*paint);
+}
+
+static void drawPoint(JNIEnv*, jobject, jlong canvasHandle, jfloat x, jfloat y,
+ jlong paintHandle) {
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawPoint(x, y, *paint);
+}
+
+static void drawPoints(JNIEnv* env, jobject, jlong canvasHandle, jfloatArray jptsArray,
+ jint offset, jint count, jlong paintHandle) {
+ NPE_CHECK_RETURN_VOID(env, jptsArray);
+ AutoJavaFloatArray autoPts(env, jptsArray);
+ float* floats = autoPts.ptr();
+ const int length = autoPts.length();
+
+ if ((offset | count) < 0 || offset + count > length) {
+ doThrowAIOOBE(env);
+ return;
+ }
+
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawPoints(floats + offset, count, *paint);
+}
+
+static void drawLine(JNIEnv* env, jobject, jlong canvasHandle, jfloat startX, jfloat startY,
+ jfloat stopX, jfloat stopY, jlong paintHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawLine(startX, startY, stopX, stopY, *paint);
+}
+
+static void drawLines(JNIEnv* env, jobject, jlong canvasHandle, jfloatArray jptsArray,
+ jint offset, jint count, jlong paintHandle) {
+ NPE_CHECK_RETURN_VOID(env, jptsArray);
+ AutoJavaFloatArray autoPts(env, jptsArray);
+ float* floats = autoPts.ptr();
+ const int length = autoPts.length();
+
+ if ((offset | count) < 0 || offset + count > length) {
+ doThrowAIOOBE(env);
+ return;
+ }
+
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawLines(floats + offset, count, *paint);
+}
+
+static void drawRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+ jfloat right, jfloat bottom, jlong paintHandle) {
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawRect(left, top, right, bottom, *paint);
+}
+
+static void drawRoundRect(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+ jfloat right, jfloat bottom, jfloat rx, jfloat ry, jlong paintHandle) {
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawRoundRect(left, top, right, bottom, rx, ry, *paint);
+}
+
+static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx, jfloat cy,
+ jfloat radius, jlong paintHandle) {
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawCircle(cx, cy, radius, *paint);
+}
+
+static void drawOval(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+ jfloat right, jfloat bottom, jlong paintHandle) {
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawOval(left, top, right, bottom, *paint);
+}
+
+static void drawArc(JNIEnv* env, jobject, jlong canvasHandle, jfloat left, jfloat top,
+ jfloat right, jfloat bottom, jfloat startAngle, jfloat sweepAngle,
+ jboolean useCenter, jlong paintHandle) {
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawArc(left, top, right, bottom, startAngle, sweepAngle,
+ useCenter, *paint);
+}
+
+static void drawPath(JNIEnv* env, jobject, jlong canvasHandle, jlong pathHandle,
+ jlong paintHandle) {
+ const SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawPath(*path, *paint);
+}
+
+static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
+ jint modeHandle, jint vertexCount,
+ jfloatArray jverts, jint vertIndex,
+ jfloatArray jtexs, jint texIndex,
+ jintArray jcolors, jint colorIndex,
+ jshortArray jindices, jint indexIndex,
+ jint indexCount, jlong paintHandle) {
+ AutoJavaFloatArray vertA(env, jverts, vertIndex + vertexCount);
+ AutoJavaFloatArray texA(env, jtexs, texIndex + vertexCount);
+ AutoJavaIntArray colorA(env, jcolors, colorIndex + vertexCount);
+ AutoJavaShortArray indexA(env, jindices, indexIndex + indexCount);
+
+ const float* verts = vertA.ptr() + vertIndex;
+ const float* texs = texA.ptr() + vertIndex;
+ const int* colors = NULL;
+ const uint16_t* indices = NULL;
+
+ if (jcolors != NULL) {
+ colors = colorA.ptr() + colorIndex;
+ }
+ if (jindices != NULL) {
+ indices = (const uint16_t*)(indexA.ptr() + indexIndex);
+ }
+
+ SkCanvas::VertexMode mode = static_cast<SkCanvas::VertexMode>(modeHandle);
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawVertices(mode, vertexCount, verts, texs, colors,
+ indices, indexCount, *paint);
+}
+
+static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jlong bitmapHandle,
+ jfloat left, jfloat top, jlong paintHandle, jint canvasDensity,
+ jint screenDensity, jint bitmapDensity) {
+ Canvas* canvas = get_canvas(canvasHandle);
+ const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+
+ if (canvasDensity == bitmapDensity || canvasDensity == 0 || bitmapDensity == 0) {
+ if (screenDensity != 0 && screenDensity != bitmapDensity) {
+ SkPaint filteredPaint;
+ if (paint) {
+ filteredPaint = *paint;
+ }
+ filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
+ canvas->drawBitmap(*bitmap, left, top, &filteredPaint);
+ } else {
+ canvas->drawBitmap(*bitmap, left, top, paint);
+ }
+ } else {
+ canvas->save(SkCanvas::kMatrixClip_SaveFlag);
+ SkScalar scale = canvasDensity / (float)bitmapDensity;
+ canvas->translate(left, top);
+ canvas->scale(scale, scale);
+
+ SkPaint filteredPaint;
+ if (paint) {
+ filteredPaint = *paint;
+ }
+ filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
+
+ canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint);
+ canvas->restore();
+ }
+}
+
+static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+ jlong matrixHandle, jlong paintHandle) {
+ const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+ const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawBitmap(*bitmap, *matrix, paint);
+}
+
+static void drawBitmapRect(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+ float srcLeft, float srcTop, float srcRight, float srcBottom,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ jlong paintHandle, jint screenDensity, jint bitmapDensity) {
+ Canvas* canvas = get_canvas(canvasHandle);
+ const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+
+ if (screenDensity != 0 && screenDensity != bitmapDensity) {
+ SkPaint filteredPaint;
+ if (paint) {
+ filteredPaint = *paint;
+ }
+ filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel);
+ canvas->drawBitmap(*bitmap, srcLeft, srcTop, srcRight, srcBottom,
+ dstLeft, dstTop, dstRight, dstBottom, &filteredPaint);
+ } else {
+ canvas->drawBitmap(*bitmap, srcLeft, srcTop, srcRight, srcBottom,
+ dstLeft, dstTop, dstRight, dstBottom, paint);
+ }
+}
+
+static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle,
+ jintArray jcolors, jint offset, jint stride,
+ jfloat x, jfloat y, jint width, jint height,
+ jboolean hasAlpha, jlong paintHandle) {
+ // Note: If hasAlpha is false, kRGB_565_SkColorType will be used, which will
+ // correct the alphaType to kOpaque_SkAlphaType.
+ SkImageInfo info = SkImageInfo::Make(width, height,
+ hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType,
+ kPremul_SkAlphaType);
+ SkBitmap bitmap;
+ if (!bitmap.allocPixels(info)) {
+ return;
+ }
+
+ if (!GraphicsJNI::SetPixels(env, jcolors, offset, stride, 0, 0, width, height, bitmap)) {
+ return;
+ }
+
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawBitmap(bitmap, x, y, paint);
+}
+
+static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+ jint meshWidth, jint meshHeight, jfloatArray jverts,
+ jint vertIndex, jintArray jcolors, jint colorIndex, jlong paintHandle) {
+ const int ptCount = (meshWidth + 1) * (meshHeight + 1);
+ AutoJavaFloatArray vertA(env, jverts, vertIndex + (ptCount << 1));
+ AutoJavaIntArray colorA(env, jcolors, colorIndex + ptCount);
+
+ const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+ const SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ get_canvas(canvasHandle)->drawBitmapMesh(*bitmap, meshWidth, meshHeight,
+ vertA.ptr(), colorA.ptr(), paint);
+}
+
+static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
+ jint index, jint count, jfloat x, jfloat y, jint bidiFlags,
+ jlong paintHandle, jlong typefaceHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ jchar* jchars = env->GetCharArrayElements(text, NULL);
+ const char* textArray = reinterpret_cast<const char*>(jchars) + index;
+ get_canvas(canvasHandle)->drawText(textArray, 0, count, count, x, y, bidiFlags, *paint, typeface);
+ env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
+}
+
+static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring text,
+ jint start, jint end, jfloat x, jfloat y, jint bidiFlags,
+ jlong paintHandle, jlong typefaceHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+ const int count = end - start;
+ const jchar* jchars = env->GetStringChars(text, NULL);
+ const char* textArray = reinterpret_cast<const char*>(jchars) + start;
+ get_canvas(canvasHandle)->drawText(textArray, 0, count, count, x, y, bidiFlags, *paint, typeface);
+ env->ReleaseStringChars(text, jchars);
+}
+
+static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index,
+ jint count, jint contextIndex, jint contextCount, jfloat x, jfloat y,
+ jboolean isRtl, jlong paintHandle, jlong typefaceHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
+ const int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
+ jchar* jchars = env->GetCharArrayElements(text, NULL);
+ const char* textArray = reinterpret_cast<const char*>(jchars) + contextIndex;
+ get_canvas(canvasHandle)->drawText(textArray, index - contextIndex, count, contextCount,
+ x, y, bidiFlags, *paint, typeface);
+ env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
+}
+
+static void drawTextRunString(JNIEnv* env, jobject obj, jlong canvasHandle, jstring text,
+ jint start, jint end, jint contextStart, jint contextEnd,
+ jfloat x, jfloat y, jboolean isRtl, jlong paintHandle,
+ jlong typefaceHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
+ int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
+ jint count = end - start;
+ jint contextCount = contextEnd - contextStart;
+ const jchar* jchars = env->GetStringChars(text, NULL);
+ const char* textArray = reinterpret_cast<const char*>(jchars) + contextStart;
+ get_canvas(canvasHandle)->drawText(textArray, start - contextStart, count, contextCount,
+ x, y, bidiFlags, *paint, typeface);
+ env->ReleaseStringChars(text, jchars);
+}
+
+static void drawPosTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
+ jint index, jint count, jfloatArray pos, jlong paintHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ jchar* jchars = text ? env->GetCharArrayElements(text, NULL) : NULL;
+ float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
+ int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
+
+ const char* textArray = reinterpret_cast<const char*>(jchars) + index;
+ get_canvas(canvasHandle)->drawPosText(textArray, posArray, count << 1, posCount, *paint);
+
+ if (text) {
+ env->ReleaseCharArrayElements(text, jchars, 0);
+ }
+ if (pos) {
+ env->ReleaseFloatArrayElements(pos, posArray, 0);
+ }
+}
+
+
+static void drawPosTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring text,
+ jfloatArray pos, jlong paintHandle) {
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ const jchar* jchars = text ? env->GetStringChars(text, NULL) : NULL;
+ int byteLength = text ? env->GetStringLength(text) : 0;
+ float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
+ int posCount = pos ? env->GetArrayLength(pos) >> 1: 0;
+
+ const char* textArray = reinterpret_cast<const char*>(jchars);
+ get_canvas(canvasHandle)->drawPosText(textArray , posArray, byteLength << 1, posCount, *paint);
+
+ if (text) {
+ env->ReleaseStringChars(text, jchars);
+ }
+ if (pos) {
+ env->ReleaseFloatArrayElements(pos, posArray, 0);
+ }
+}
+
+class DrawTextOnPathFunctor {
+public:
+ DrawTextOnPathFunctor(const Layout& layout, Canvas* canvas, float hOffset,
+ float vOffset, const SkPaint& paint, const SkPath& path)
+ : layout(layout), canvas(canvas), hOffset(hOffset), vOffset(vOffset),
+ paint(paint), path(path) {
+ }
+ void operator()(size_t start, size_t end) {
+ uint16_t glyphs[1];
+ for (size_t i = start; i < end; i++) {
+ glyphs[0] = layout.getGlyphId(i);
+ float x = hOffset + layout.getX(i);
+ float y = vOffset + layout.getY(i);
+ canvas->drawTextOnPath((const char*) glyphs, 1, path, x, y, paint);
+ }
+ }
+private:
+ const Layout& layout;
+ Canvas* canvas;
+ float hOffset;
+ float vOffset;
+ const SkPaint& paint;
+ const SkPath& path;
+};
+
+static void drawTextOnPath(Canvas* canvas, const char* text, int count, int bidiFlags,
+ const SkPath& path, float hOffset, float vOffset,
+ const SkPaint& paint, TypefaceImpl* typeface) {
+ SkPaint paintCopy(paint);
+ Layout layout;
+ std::string css = MinikinUtils::setLayoutProperties(&layout, &paintCopy, bidiFlags, typeface);
+ layout.doLayout((uint16_t*)text, 0, count, count, css);
+ hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path);
+
+ // Set align to left for drawing, as we don't want individual
+ // glyphs centered or right-aligned; the offset above takes
+ // care of all alignment.
+ paintCopy.setTextAlign(SkPaint::kLeft_Align);
+
+ DrawTextOnPathFunctor f(layout, canvas, hOffset, vOffset, paintCopy, path);
+ MinikinUtils::forFontRun(layout, &paintCopy, f);
+}
+
+static void drawTextOnPathChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
+ jint index, jint count, jlong pathHandle, jfloat hOffset,
+ jfloat vOffset, jint bidiFlags, jlong paintHandle,
+ jlong typefaceHandle) {
+ SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
+ jchar* jchars = env->GetCharArrayElements(text, NULL);
+ const char* textArray = reinterpret_cast<const char*>(jchars);
+
+ drawTextOnPath(get_canvas(canvasHandle), textArray + index, count, bidiFlags, *path,
+ hOffset, vOffset, *paint, typeface);
+
+ env->ReleaseCharArrayElements(text, jchars, 0);
+}
+
+static void drawTextOnPathString(JNIEnv* env, jobject, jlong canvasHandle, jstring text,
+ jlong pathHandle, jfloat hOffset, jfloat vOffset,
+ jint bidiFlags, jlong paintHandle, jlong typefaceHandle) {
+ SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+ SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+ TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
+
+ const jchar* jchars = env->GetStringChars(text, NULL);
+ const char* textArray = reinterpret_cast<const char*>(jchars);
+ int count = env->GetStringLength(text);
+
+ drawTextOnPath(get_canvas(canvasHandle), textArray, count, bidiFlags, *path,
+ hOffset, vOffset, *paint, typeface);
+
+ env->ReleaseStringChars(text, jchars);
+}
+
+static void setDrawFilter(JNIEnv* env, jobject, jlong canvasHandle, jlong filterHandle) {
+ get_canvas(canvasHandle)->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
+}
+
+static void freeCaches(JNIEnv* env, jobject) {
+ SkGraphics::PurgeFontCache();
+}
+
+static void freeTextLayoutCaches(JNIEnv* env, jobject) {
+ Layout::purgeCaches();
+}
+
+}; // namespace CanvasJNI
+
+static JNINativeMethod gMethods[] = {
+ {"finalizer", "(J)V", (void*) CanvasJNI::finalizer},
+ {"initRaster", "(J)J", (void*) CanvasJNI::initRaster},
+ {"native_setBitmap", "(JJZ)V", (void*) CanvasJNI::setBitmap},
+ {"native_isOpaque","(J)Z", (void*) CanvasJNI::isOpaque},
+ {"native_getWidth","(J)I", (void*) CanvasJNI::getWidth},
+ {"native_getHeight","(J)I", (void*) CanvasJNI::getHeight},
+ {"native_save","(JI)I", (void*) CanvasJNI::save},
+ {"native_saveLayer","(JFFFFJI)I", (void*) CanvasJNI::saveLayer},
+ {"native_saveLayerAlpha","(JFFFFII)I", (void*) CanvasJNI::saveLayerAlpha},
+ {"native_getSaveCount","(J)I", (void*) CanvasJNI::getSaveCount},
+ {"native_restore","(J)V", (void*) CanvasJNI::restore},
+ {"native_restoreToCount","(JI)V", (void*) CanvasJNI::restoreToCount},
+ {"native_getCTM", "(JJ)V", (void*)CanvasJNI::getCTM},
+ {"native_setMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
+ {"native_concat","(JJ)V", (void*) CanvasJNI::concat},
+ {"native_rotate","(JF)V", (void*) CanvasJNI::rotate},
+ {"native_scale","(JFF)V", (void*) CanvasJNI::scale},
+ {"native_skew","(JFF)V", (void*) CanvasJNI::skew},
+ {"native_translate","(JFF)V", (void*) CanvasJNI::translate},
+ {"native_getClipBounds","(JLandroid/graphics/Rect;)Z", (void*) CanvasJNI::getClipBounds},
+ {"native_quickReject","(JJ)Z", (void*) CanvasJNI::quickRejectPath},
+ {"native_quickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
+ {"native_clipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
+ {"native_clipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
+ {"native_clipRegion","(JJI)Z", (void*) CanvasJNI::clipRegion},
+ {"native_drawColor","(JII)V", (void*) CanvasJNI::drawColor},
+ {"native_drawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
+ {"native_drawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
+ {"native_drawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
+ {"native_drawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
+ {"native_drawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
+ {"native_drawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
+ {"native_drawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
+ {"native_drawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle},
+ {"native_drawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval},
+ {"native_drawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
+ {"native_drawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
+ {"nativeDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
+ {"native_drawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap},
+ {"nativeDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
+ {"native_drawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
+ {"native_drawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
+ {"nativeDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
+ {"native_drawText","(J[CIIFFIJJ)V", (void*) CanvasJNI::drawTextChars},
+ {"native_drawText","(JLjava/lang/String;IIFFIJJ)V", (void*) CanvasJNI::drawTextString},
+ {"native_drawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
+ {"native_drawTextRun","(JLjava/lang/String;IIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunString},
+ {"native_drawTextOnPath","(J[CIIJFFIJJ)V", (void*) CanvasJNI::drawTextOnPathChars},
+ {"native_drawTextOnPath","(JLjava/lang/String;JFFIJJ)V", (void*) CanvasJNI::drawTextOnPathString},
+ {"nativeSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setDrawFilter},
+ {"freeCaches", "()V", (void*) CanvasJNI::freeCaches},
+ {"freeTextLayoutCaches", "()V", (void*) CanvasJNI::freeTextLayoutCaches}
+};
+
+int register_android_graphics_Canvas(JNIEnv* env) {
+ return AndroidRuntime::registerNativeMethods(env, "android/graphics/Canvas", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_graphics_Picture.cpp b/core/jni/android_graphics_Picture.cpp
index f827907..eb8f6dd 100644
--- a/core/jni/android_graphics_Picture.cpp
+++ b/core/jni/android_graphics_Picture.cpp
@@ -51,7 +51,7 @@
static void android_graphics_Picture_draw(JNIEnv* env, jobject, jlong canvasHandle,
jlong pictureHandle) {
- SkCanvas* canvas = GraphicsJNI::getNativeCanvas(canvasHandle);
+ Canvas* canvas = reinterpret_cast<Canvas*>(canvasHandle);
Picture* picture = reinterpret_cast<Picture*>(pictureHandle);
SkASSERT(canvas);
SkASSERT(picture);
@@ -84,12 +84,7 @@
static jlong android_graphics_Picture_beginRecording(JNIEnv* env, jobject, jlong pictHandle,
jint w, jint h) {
Picture* pict = reinterpret_cast<Picture*>(pictHandle);
- // beginRecording does not ref its return value, it just returns it.
- SkCanvas* canvas = pict->beginRecording(w, h);
- // the java side will wrap this guy in a Canvas.java, which will call
- // unref in its finalizer, so we have to ref it here, so that both that
- // Canvas.java and our picture can both be owners
- canvas->ref();
+ Canvas* canvas = pict->beginRecording(w, h);
return reinterpret_cast<jlong>(canvas);
}
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 9621bb2..d2f5b5d 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -19,17 +19,20 @@
#include <utils/Log.h>
#include <utils/Errors.h>
#include <utils/Trace.h>
+#include <camera/CameraUtils.h>
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/android_view_Surface.h"
+#include "android_runtime/android_graphics_SurfaceTexture.h"
#include <gui/Surface.h>
#include <gui/IGraphicBufferProducer.h>
#include <ui/GraphicBuffer.h>
#include <system/window.h>
#include <hardware/camera3.h>
+#include <system/camera_metadata.h>
#include <stdint.h>
#include <inttypes.h>
@@ -335,6 +338,25 @@
return anw;
}
+static sp<ANativeWindow> getNativeWindowFromTexture(JNIEnv* env, jobject surfaceTexture) {
+ sp<ANativeWindow> anw;
+ if (surfaceTexture) {
+ anw = android_SurfaceTexture_getNativeWindow(env, surfaceTexture);
+ if (env->ExceptionCheck()) {
+ return NULL;
+ }
+ } else {
+ jniThrowNullPointerException(env, "surfaceTexture");
+ return NULL;
+ }
+ if (anw == NULL) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "SurfaceTexture had no valid native window.");
+ return NULL;
+ }
+ return anw;
+}
+
static sp<Surface> getSurface(JNIEnv* env, jobject surface) {
sp<Surface> s;
if (surface) {
@@ -376,6 +398,17 @@
static jint LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
jobject surface, jintArray dimens) {
ALOGV("nativeGetSurfaceDimens");
+
+ if (dimens == NULL) {
+ ALOGE("%s: Null dimens argument passed to nativeDetectSurfaceDimens", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ if (env->GetArrayLength(dimens) < 2) {
+ ALOGE("%s: Invalid length of dimens argument in nativeDetectSurfaceDimens", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
@@ -398,6 +431,37 @@
return NO_ERROR;
}
+static jint LegacyCameraDevice_nativeDetectTextureDimens(JNIEnv* env, jobject thiz,
+ jobject surfaceTexture, jintArray dimens) {
+ ALOGV("nativeDetectTextureDimens");
+ sp<ANativeWindow> anw;
+ if ((anw = getNativeWindowFromTexture(env, surfaceTexture)) == NULL) {
+ ALOGE("%s: Could not retrieve native window from SurfaceTexture.", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ int32_t dimenBuf[2];
+ status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
+ if(err != NO_ERROR) {
+ ALOGE("%s: Error while querying SurfaceTexture width %s (%d)", __FUNCTION__,
+ strerror(-err), err);
+ return err;
+ }
+
+ err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
+ if(err != NO_ERROR) {
+ ALOGE("%s: Error while querying SurfaceTexture height %s (%d)", __FUNCTION__,
+ strerror(-err), err);
+ return err;
+ }
+
+ env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
+ if (env->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+ return NO_ERROR;
+}
+
static jint LegacyCameraDevice_nativeConfigureSurface(JNIEnv* env, jobject thiz, jobject surface,
jint width, jint height, jint pixelFormat) {
ALOGV("nativeConfigureSurface");
@@ -504,6 +568,42 @@
return reinterpret_cast<jlong>(b.get());
}
+static jint LegacyCameraDevice_nativeSetSurfaceOrientation(JNIEnv* env, jobject thiz,
+ jobject surface, jint facing, jint orientation) {
+ ALOGV("nativeSetSurfaceOrientation");
+ sp<ANativeWindow> anw;
+ if ((anw = getNativeWindow(env, surface)) == NULL) {
+ ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ status_t err = NO_ERROR;
+ CameraMetadata staticMetadata;
+
+ int32_t orientVal = static_cast<int32_t>(orientation);
+ uint8_t facingVal = static_cast<uint8_t>(facing);
+ staticMetadata.update(ANDROID_SENSOR_ORIENTATION, &orientVal, 1);
+ staticMetadata.update(ANDROID_LENS_FACING, &facingVal, 1);
+
+ int32_t transform = 0;
+
+ if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != OK) {
+ ALOGE("%s: Invalid rotation transform %s (%d)", __FUNCTION__, strerror(-err),
+ err);
+ return err;
+ }
+
+ ALOGV("%s: Setting buffer sticky transform to %d", __FUNCTION__, transform);
+
+ if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != OK) {
+ ALOGE("%s: Unable to configure surface transform, error %s (%d)", __FUNCTION__,
+ strerror(-err), err);
+ return err;
+ }
+
+ return NO_ERROR;
+}
+
} // extern "C"
static JNINativeMethod gCameraDeviceMethods[] = {
@@ -528,6 +628,12 @@
{ "nativeGetSurfaceId",
"(Landroid/view/Surface;)J",
(void *)LegacyCameraDevice_nativeGetSurfaceId },
+ { "nativeDetectTextureDimens",
+ "(Landroid/graphics/SurfaceTexture;[I)I",
+ (void *)LegacyCameraDevice_nativeDetectTextureDimens },
+ { "nativeSetSurfaceOrientation",
+ "(Landroid/view/Surface;II)I",
+ (void *)LegacyCameraDevice_nativeSetSurfaceOrientation },
};
// Get all the required offsets in java class and register native functions
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 989b60e..64366e5 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -439,8 +439,11 @@
if (!is_system_server) {
int rc = createProcessGroup(uid, getpid());
if (rc != 0) {
- ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc));
- RuntimeAbort(env);
+ if (rc == -EROFS) {
+ ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
+ } else {
+ ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc));
+ }
}
}
diff --git a/core/res/res/anim/launch_task_behind_background.xml b/core/res/res/anim/launch_task_behind_background.xml
new file mode 100644
index 0000000..358511f
--- /dev/null
+++ b/core/res/res/anim/launch_task_behind_background.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
+
+ <translate android:fromYDelta="110%" android:toYDelta="66%"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:startOffset="50"
+ android:duration="300" />
+
+ <translate android:fromYDelta="0%" android:toYDelta="167%"
+ android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
+ android:interpolator="@interpolator/accelerate_quint"
+ android:startOffset="433"
+ android:duration="300" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/anim/launch_task_behind_source.xml b/core/res/res/anim/launch_task_behind_source.xml
new file mode 100644
index 0000000..426ee5d
--- /dev/null
+++ b/core/res/res/anim/launch_task_behind_source.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="normal">
+
+ <alpha android:fromAlpha="1.0" android:toAlpha="0.6"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/accelerate_cubic"
+ android:duration="133"/>
+
+ <translate android:fromYDelta="0" android:toYDelta="10%"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/accelerate_cubic"
+ android:duration="350"/>
+
+ <scale android:fromXScale="1.0" android:toXScale="0.9"
+ android:fromYScale="1.0" android:toYScale="0.9"
+ android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+ android:pivotX="50%p" android:pivotY="50%p"
+ android:interpolator="@interpolator/fast_out_slow_in"
+ android:duration="350" />
+
+ <alpha android:fromAlpha="1.0" android:toAlpha="1.6"
+ android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_cubic"
+ android:startOffset="433"
+ android:duration="133"/>
+
+ <translate android:fromYDelta="0%" android:toYDelta="-10%"
+ android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_cubic"
+ android:startOffset="433"
+ android:duration="350"/>
+
+ <scale android:fromXScale="1.0" android:toXScale="1.1"
+ android:fromYScale="1.0" android:toYScale="1.1"
+ android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
+ android:pivotX="50%p" android:pivotY="50%p"
+ android:interpolator="@interpolator/decelerate_cubic"
+ android:startOffset="433"
+ android:duration="350" />
+</set>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_calendar_holo_dark.xml b/core/res/res/color/date_picker_calendar_holo_dark.xml
new file mode 100644
index 0000000..d29486f
--- /dev/null
+++ b/core/res/res/color/date_picker_calendar_holo_dark.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_enabled="true" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_holo_dark"/>
+ <item android:state_enabled="true" android:state_selected="true"
+ android:color="@color/holo_blue_light"/>
+ <item android:state_enabled="false"
+ android:color="@color/datepicker_default_disabled_text_color_holo_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_calendar_holo_light.xml b/core/res/res/color/date_picker_calendar_holo_light.xml
new file mode 100644
index 0000000..776f39b
--- /dev/null
+++ b/core/res/res/color/date_picker_calendar_holo_light.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_enabled="true" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_holo_light"/>
+ <item android:state_enabled="true" android:state_selected="true"
+ android:color="@color/holo_blue_light"/>
+ <item android:state_enabled="false"
+ android:color="@color/datepicker_default_disabled_text_color_holo_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_calendar_material_dark.xml b/core/res/res/color/date_picker_calendar_material_dark.xml
new file mode 100644
index 0000000..86a0673
--- /dev/null
+++ b/core/res/res/color/date_picker_calendar_material_dark.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_enabled="true" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_material_dark"/>
+ <item android:state_enabled="true" android:state_selected="true"
+ android:color="@color/holo_blue_light"/>
+ <item android:state_enabled="false"
+ android:color="@color/datepicker_default_disabled_text_color_material_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_calendar_material_light.xml b/core/res/res/color/date_picker_calendar_material_light.xml
new file mode 100644
index 0000000..015eed7
--- /dev/null
+++ b/core/res/res/color/date_picker_calendar_material_light.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_enabled="true" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_material_light"/>
+ <item android:state_enabled="true" android:state_selected="true"
+ android:color="@color/holo_blue_light"/>
+ <item android:state_enabled="false"
+ android:color="@color/datepicker_default_disabled_text_color_material_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_selector_holo_dark.xml b/core/res/res/color/date_picker_selector_holo_dark.xml
new file mode 100644
index 0000000..9e5a5bd
--- /dev/null
+++ b/core/res/res/color/date_picker_selector_holo_dark.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:color="@color/datepicker_default_pressed_text_color_holo_dark"/>
+ <item android:state_pressed="false" android:state_selected="true"
+ android:color="@color/datepicker_default_selected_text_color_holo_dark"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_holo_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_selector_holo_light.xml b/core/res/res/color/date_picker_selector_holo_light.xml
new file mode 100644
index 0000000..bf8667c
--- /dev/null
+++ b/core/res/res/color/date_picker_selector_holo_light.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:color="@color/datepicker_default_pressed_text_color_holo_light"/>
+ <item android:state_pressed="false" android:state_selected="true"
+ android:color="@color/datepicker_default_selected_text_color_holo_light"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_holo_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_selector_material_dark.xml b/core/res/res/color/date_picker_selector_material_dark.xml
new file mode 100644
index 0000000..e407387
--- /dev/null
+++ b/core/res/res/color/date_picker_selector_material_dark.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:color="@color/datepicker_default_pressed_text_color_material_dark"/>
+ <item android:state_pressed="false" android:state_selected="true"
+ android:color="@color/datepicker_default_selected_text_color_material_dark"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_material_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_selector_material_light.xml b/core/res/res/color/date_picker_selector_material_light.xml
new file mode 100644
index 0000000..b4c6a47
--- /dev/null
+++ b/core/res/res/color/date_picker_selector_material_light.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:color="@color/datepicker_default_pressed_text_color_material_light"/>
+ <item android:state_pressed="false" android:state_selected="true"
+ android:color="@color/datepicker_default_selected_text_color_material_light"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_material_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_year_selector_holo_dark.xml b/core/res/res/color/date_picker_year_selector_holo_dark.xml
new file mode 100644
index 0000000..ce519b2
--- /dev/null
+++ b/core/res/res/color/date_picker_year_selector_holo_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:color="@color/datepicker_default_pressed_text_color_holo_dark"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_holo_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_year_selector_holo_light.xml b/core/res/res/color/date_picker_year_selector_holo_light.xml
new file mode 100644
index 0000000..c228711
--- /dev/null
+++ b/core/res/res/color/date_picker_year_selector_holo_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:color="@color/datepicker_default_pressed_text_color_holo_light"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_holo_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_year_selector_material_dark.xml b/core/res/res/color/date_picker_year_selector_material_dark.xml
new file mode 100644
index 0000000..b5ff09a
--- /dev/null
+++ b/core/res/res/color/date_picker_year_selector_material_dark.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:color="@color/datepicker_default_pressed_text_color_material_dark"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_material_dark"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/date_picker_year_selector_material_light.xml b/core/res/res/color/date_picker_year_selector_material_light.xml
new file mode 100644
index 0000000..5e329b3
--- /dev/null
+++ b/core/res/res/color/date_picker_year_selector_material_light.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="true"
+ android:color="@color/datepicker_default_pressed_text_color_material_light"/>
+ <item android:state_pressed="false" android:state_selected="false"
+ android:color="@color/datepicker_default_normal_text_color_material_light"/>
+
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/background_leanback_setup.xml b/core/res/res/drawable/background_leanback_setup.xml
new file mode 100644
index 0000000..ca3392c
--- /dev/null
+++ b/core/res/res/drawable/background_leanback_setup.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:drawable="@color/black"/>
+</layer-list>
diff --git a/core/res/res/layout-land/date_picker_holo.xml b/core/res/res/layout-land/date_picker_holo.xml
new file mode 100644
index 0000000..98e26ca
--- /dev/null
+++ b/core/res/res/layout-land/date_picker_holo.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ 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="match_parent"
+ android:layout_height="@dimen/datepicker_view_animator_height"
+ android:gravity="center"
+ android:orientation="horizontal" >
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:background="?android:attr/datePickerHeaderSelectorBackgroundColor"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:orientation="vertical" >
+
+ <include layout="@layout/date_picker_header_view" />
+
+ <include layout="@layout/date_picker_selected_date" />
+ </LinearLayout>
+
+ <include layout="@layout/date_picker_done_button" />
+ </LinearLayout>
+
+ <include layout="@layout/date_picker_view_animator" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout-sw600dp/date_picker_holo.xml b/core/res/res/layout-sw600dp/date_picker_holo.xml
new file mode 100644
index 0000000..847aa32
--- /dev/null
+++ b/core/res/res/layout-sw600dp/date_picker_holo.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ 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="match_parent"
+ android:background="@color/datepicker_default_view_animator_color_holo_light"
+ android:gravity="center"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/datepicker_selected_calendar_layout_height"
+ android:orientation="vertical" >
+
+ <include layout="@layout/date_picker_header_view" />
+
+ <include layout="@layout/date_picker_selected_date" />
+ </LinearLayout>
+
+ <include layout="@layout/date_picker_view_animator" />
+
+ <include layout="@layout/date_picker_done_button" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/alert_dialog_material.xml b/core/res/res/layout/alert_dialog_material.xml
index 93acc3f..6a435b2 100644
--- a/core/res/res/layout/alert_dialog_material.xml
+++ b/core/res/res/layout/alert_dialog_material.xml
@@ -20,13 +20,7 @@
android:id="@+id/parentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@drawable/dialog_background_material"
- android:translationZ="@dimen/floating_window_z"
- android:layout_marginLeft="@dimen/floating_window_margin_left"
- android:layout_marginTop="@dimen/floating_window_margin_top"
- android:layout_marginRight="@dimen/floating_window_margin_right"
- android:layout_marginBottom="@dimen/floating_window_margin_bottom">
+ android:orientation="vertical">
<LinearLayout android:id="@+id/topPanel"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/date_picker_done_button.xml b/core/res/res/layout/date_picker_done_button.xml
new file mode 100644
index 0000000..b8e8c03
--- /dev/null
+++ b/core/res/res/layout/date_picker_done_button.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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:id="@+id/layout_buttons"
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="@dimen/datepicker_component_width"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="beginning" >
+
+ <Button
+ android:id="@+id/done"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:text="@string/done_label"
+ android:textSize="@dimen/datepicker_done_label_size" />
+</LinearLayout>
diff --git a/core/res/res/layout/date_picker_header_view.xml b/core/res/res/layout/date_picker_header_view.xml
new file mode 100644
index 0000000..eccdd3e
--- /dev/null
+++ b/core/res/res/layout/date_picker_header_view.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/date_picker_header"
+ android:layout_width="@dimen/datepicker_component_width"
+ android:layout_height="@dimen/datepicker_header_height"
+ android:background="?android:attr/datePickerHeaderDayOfWeekLabelBackgroundColor"
+ android:gravity="center"
+ android:textAppearance="?android:attr/datePickerHeaderDayOfWeekLabelTextAppearance"
+ android:importantForAccessibility="no"
+ android:textAllCaps="true"
+ />
diff --git a/core/res/res/layout/date_picker_holo.xml b/core/res/res/layout/date_picker_holo.xml
index b465d97..389c2b5 100644
--- a/core/res/res/layout/date_picker_holo.xml
+++ b/core/res/res/layout/date_picker_holo.xml
@@ -1,92 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** 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.
-*/
--->
+ Copyright (C) 2011 The Android Open Source Project
-<!-- Layout of date picker-->
+ 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
-<!-- Warning: everything within the "pickers" layout is removed and re-ordered
- depending on the date format selected by the user.
+ 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="center_horizontal"
- android:orientation="horizontal"
- android:gravity="center">
+ android:layout_width="@dimen/datepicker_component_width"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical" >
- <LinearLayout android:id="@+id/pickers"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:gravity="center">
-
- <!-- Month -->
- <NumberPicker
- android:id="@+id/month"
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="16dip"
- android:layout_marginBottom="16dip"
- android:layout_marginStart="8dip"
- android:layout_marginEnd="8dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
+ android:orientation="vertical" >
- <!-- Day -->
- <NumberPicker
- android:id="@+id/day"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dip"
- android:layout_marginBottom="16dip"
- android:layout_marginStart="8dip"
- android:layout_marginEnd="8dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
+ <include layout="@layout/date_picker_header_view" />
- <!-- Year -->
- <NumberPicker
- android:id="@+id/year"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dip"
- android:layout_marginBottom="16dip"
- android:layout_marginStart="8dip"
- android:layout_marginEnd="16dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
-
+ <include layout="@layout/date_picker_selected_date" />
</LinearLayout>
- <!-- calendar view -->
- <CalendarView
- android:id="@+id/calendar_view"
- android:layout_width="245dip"
- android:layout_height="280dip"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:layout_weight="1"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
+ <include layout="@layout/date_picker_view_animator" />
-</LinearLayout>
+ <include layout="@layout/date_picker_done_button" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker_legacy.xml
similarity index 100%
rename from core/res/res/layout/date_picker.xml
rename to core/res/res/layout/date_picker_legacy.xml
diff --git a/core/res/res/layout/date_picker_legacy_holo.xml b/core/res/res/layout/date_picker_legacy_holo.xml
new file mode 100644
index 0000000..b465d97
--- /dev/null
+++ b/core/res/res/layout/date_picker_legacy_holo.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** 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.
+*/
+-->
+
+<!-- Layout of date picker-->
+
+<!-- Warning: everything within the "pickers" layout is removed and re-ordered
+ depending on the date format selected by the user.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <LinearLayout android:id="@+id/pickers"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <!-- Month -->
+ <NumberPicker
+ android:id="@+id/month"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dip"
+ android:layout_marginBottom="16dip"
+ android:layout_marginStart="8dip"
+ android:layout_marginEnd="8dip"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ />
+
+ <!-- Day -->
+ <NumberPicker
+ android:id="@+id/day"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dip"
+ android:layout_marginBottom="16dip"
+ android:layout_marginStart="8dip"
+ android:layout_marginEnd="8dip"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ />
+
+ <!-- Year -->
+ <NumberPicker
+ android:id="@+id/year"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dip"
+ android:layout_marginBottom="16dip"
+ android:layout_marginStart="8dip"
+ android:layout_marginEnd="16dip"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ />
+
+ </LinearLayout>
+
+ <!-- calendar view -->
+ <CalendarView
+ android:id="@+id/calendar_view"
+ android:layout_width="245dip"
+ android:layout_height="280dip"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:layout_weight="1"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ />
+
+</LinearLayout>
diff --git a/core/res/res/layout/date_picker_selected_date.xml b/core/res/res/layout/date_picker_selected_date.xml
new file mode 100644
index 0000000..426deed
--- /dev/null
+++ b/core/res/res/layout/date_picker_selected_date.xml
@@ -0,0 +1,65 @@
+<?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:id="@+id/day_picker_selector_layout"
+ android:layout_width="@dimen/datepicker_component_width"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"
+ android:background="?android:attr/datePickerHeaderSelectorBackgroundColor"
+ android:gravity="center"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/date_picker_month_and_day_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:clickable="true"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/date_picker_month"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:duplicateParentState="true"
+ android:gravity="center_horizontal|bottom"
+ android:textAppearance="?android:attr/datePickerHeaderSelectorMonthLabelTextAppearance" />
+
+ <TextView
+ android:id="@+id/date_picker_day"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginBottom="-10dip"
+ android:layout_marginTop="-10dip"
+ android:duplicateParentState="true"
+ android:gravity="center"
+ android:textAppearance="?android:attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/date_picker_year"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_horizontal|top"
+ android:textAppearance="?android:attr/datePickerHeaderSelectorYearLabelTextAppearance" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/date_picker_view_animator.xml b/core/res/res/layout/date_picker_view_animator.xml
new file mode 100644
index 0000000..9085ed5
--- /dev/null
+++ b/core/res/res/layout/date_picker_view_animator.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<com.android.internal.widget.AccessibleDateAnimator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/animator"
+ android:layout_width="@dimen/datepicker_component_width"
+ android:layout_height="@dimen/datepicker_view_animator_height"
+ android:gravity="center" />
\ No newline at end of file
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index b3a3478..5acb588 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -35,7 +35,6 @@
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
- android:touchscreenBlocksFocus="true"
android:gravity="top">
<com.android.internal.widget.ActionBarView
android:id="@+id/action_bar"
@@ -54,6 +53,5 @@
android:layout_height="wrap_content"
style="?attr/actionBarSplitStyle"
android:visibility="gone"
- android:touchscreenBlocksFocus="true"
android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout/screen_toolbar.xml b/core/res/res/layout/screen_toolbar.xml
index 039e89f..56815f8 100644
--- a/core/res/res/layout/screen_toolbar.xml
+++ b/core/res/res/layout/screen_toolbar.xml
@@ -35,7 +35,6 @@
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
- android:touchscreenBlocksFocus="true"
android:gravity="top">
<Toolbar
android:id="@+id/action_bar"
diff --git a/core/res/res/layout/year_label_text_view.xml b/core/res/res/layout/year_label_text_view.xml
new file mode 100644
index 0000000..4e39831
--- /dev/null
+++ b/core/res/res/layout/year_label_text_view.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ 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.
+-->
+<android.widget.TextViewWithCircularIndicator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/month_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/datepicker_year_label_height"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:textAppearance="?android:attr/datePickerHeaderListYearLabelTextAppearance" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5fdadb7..ba11ccd 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -691,6 +691,33 @@
<!-- The DatePicker style. -->
<attr name="datePickerStyle" format="reference" />
+ <!-- The DatePicker Header day of week label background color . -->
+ <attr name="datePickerHeaderDayOfWeekLabelBackgroundColor" format="reference" />
+
+ <!-- The DatePicker Header day of week label text appearance -->
+ <attr name="datePickerHeaderDayOfWeekLabelTextAppearance" format="reference" />
+
+ <!-- The DatePicker Header selector background color . -->
+ <attr name="datePickerHeaderSelectorBackgroundColor" format="reference" />
+
+ <!-- The DatePicker Header selector month label text appearance -->
+ <attr name="datePickerHeaderSelectorMonthLabelTextAppearance" format="reference" />
+
+ <!-- The DatePicker Header selector day of month label text appearance -->
+ <attr name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance" format="reference" />
+
+ <!-- The DatePicker Header selector year label text appearance -->
+ <attr name="datePickerHeaderSelectorYearLabelTextAppearance" format="reference" />
+
+ <!-- The DatePicker Header list year label text appearance -->
+ <attr name="datePickerHeaderListYearLabelTextAppearance" format="reference" />
+
+ <!-- DatePicker list year label circle background color -->
+ <attr name="datePickerHeaderListYearLabelCircleBackgroundColor" format="reference" />
+
+ <!-- The DatePicker dialog theme. -->
+ <attr name="datePickerDialogTheme" format="reference" />
+
<!-- Default ActivityChooserView style. -->
<attr name="activityChooserViewStyle" format="reference" />
@@ -1826,6 +1853,9 @@
Activity Transition. Corresponds to
{@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. -->
<attr name="windowTransitionBackgroundFadeDuration" />
+
+ <!-- Elevation to use for the window. -->
+ <attr name="windowElevation" format="dimension" />
</declare-styleable>
<!-- The set of attributes that describe a AlertDialog's theme. -->
@@ -1889,6 +1919,14 @@
<!-- When opening an activity in a new task, this is the animation that is
run on the activity of the old task (which is exiting the screen). -->
<attr name="taskOpenExitAnimation" format="reference" />
+ <!-- When opening an activity in a new task using Intent/FLAG_ACTIVITY_LAUNCH_BEHIND,
+ this is the animation that is run on the activity of the new task (which is
+ entering the screen and then leaving). -->
+ <attr name="launchTaskBehindBackgroundAnimation" format="reference" />
+ <!-- When opening an activity in a new task using Intent.FLAG_ACTIVITY_LAUNCH_BEHIND,
+ this is the animation that is run on the activity of the old task (which is
+ already on the screen and then stays on). -->
+ <attr name="launchTaskBehindSourceAnimation" format="reference" />
<!-- When closing the last activity of a task, this is the animation that is
run on the activity of the next task (which is entering the screen). -->
<attr name="taskCloseEnterAnimation" format="reference" />
@@ -4172,6 +4210,28 @@
<attr name="maxDate" format="string" />
<!-- @hide The layout of the date picker. -->
<attr name="internalLayout" format="reference" />
+ <!-- @hide The layout of the legacy DatePicker. -->
+ <attr name="legacyLayout" />
+ <!-- @hide Enables or disable the use of the legacy layout for the DatePicker. -->
+ <attr name="legacyMode" />
+ <!-- The background color for the date selector 's day of week of the non legacy DatePicker. -->
+ <attr name="dateSelectorDayOfWeekBackgroundColor" format="color|reference" />
+ <!-- The text color for the date selector's day of week of the non legacy DatePicker. -->
+ <attr name="dateSelectorDayOfWeekTextAppearance" format="reference" />
+ <!-- The background color for the date selector of the non legacy DatePicker. -->
+ <attr name="dateSelectorBackgroundColor" format="color|reference" />
+ <!-- The month's text appearance in the date selector of the non legacy DatePicker. -->
+ <attr name="dateSelectorMonthTextAppearance" format="reference" />
+ <!-- The day of month's text appearance in the date selector of the non legacy DatePicker. -->
+ <attr name="dateSelectorDayOfMonthTextAppearance" format="reference" />
+ <!-- The year's text appearance in the date selector of the non legacy DatePicker. -->
+ <attr name="dateSelectorYearTextAppearance" format="reference" />
+ <!-- The list year's text appearance in the list of the non legacy DatePicker. -->
+ <attr name="dateSelectorYearListItemTextAppearance" format="reference" />
+ <!-- The list year's selected circle color in the list of the non legacy DatePicker. -->
+ <attr name="dateSelectorYearListSelectedCircleColor" format="color|reference" />
+ <!-- The text color list of the calendar of the non legacy DatePicker. -->
+ <attr name="calendarTextColor" format="color|reference" />
</declare-styleable>
<declare-styleable name="TwoLineListItem">
diff --git a/core/res/res/values/colors_holo.xml b/core/res/res/values/colors_holo.xml
index 97b4803..8785a567 100644
--- a/core/res/res/values/colors_holo.xml
+++ b/core/res/res/values/colors_holo.xml
@@ -120,4 +120,32 @@
<color name="timepicker_default_ampm_unselected_background_color_holo_light">@color/white</color>
<color name="timepicker_default_ampm_unselected_background_color_holo_dark">@color/transparent</color>
+
+ <!-- DatePicker colors -->
+ <eat-comment />
+
+ <color name="datepicker_default_header_selector_background_holo_light">@android:color/white</color>
+ <color name="datepicker_default_header_selector_background_holo_dark">#ff303030</color>
+
+ <color name="datepicker_default_header_dayofweek_background_color_holo_light">#999999</color>
+ <color name="datepicker_default_header_dayofweek_background_color_holo_dark">@android:color/white</color>
+
+ <color name="datepicker_default_normal_text_color_holo_light">#ff999999</color>
+ <color name="datepicker_default_normal_text_color_holo_dark">@android:color/white</color>
+
+ <color name="datepicker_default_disabled_text_color_holo_light">#80999999</color>
+ <color name="datepicker_default_disabled_text_color_holo_dark">#80999999</color>
+
+ <color name="datepicker_default_selected_text_color_holo_light">#33b5e5</color>
+ <color name="datepicker_default_selected_text_color_holo_dark">#33b5e5</color>
+
+ <color name="datepicker_default_pressed_text_color_holo_light">#0099cc</color>
+ <color name="datepicker_default_pressed_text_color_holo_dark">#0099cc</color>
+
+ <color name="datepicker_default_circle_background_color_holo_light">@android:color/holo_blue_light</color>
+ <color name="datepicker_default_circle_background_color_holo_dark">@android:color/holo_blue_light</color>
+
+ <color name="datepicker_default_view_animator_color_holo_light">#f2f2f2</color>
+ <color name="datepicker_default_view_animator_color_holo_dark">#ff303030</color>
+
</resources>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 7371d4e..c4f4891 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -166,4 +166,32 @@
<color name="timepicker_default_ampm_unselected_background_color_material">@color/transparent</color>
<color name="timepicker_default_selector_color_material">@color/material_light_blue_A200</color>
<color name="timepicker_default_numbers_background_color_material">@color/transparent</color>
+
+ <!-- DatePicker colors -->
+ <eat-comment />
+
+ <color name="datepicker_default_header_selector_background_material_light">@android:color/white</color>
+ <color name="datepicker_default_header_selector_background_material_dark">#ff303030</color>
+
+ <color name="datepicker_default_header_dayofweek_background_color_material_light">#999999</color>
+ <color name="datepicker_default_header_dayofweek_background_color_material_dark">@android:color/white</color>
+
+ <color name="datepicker_default_normal_text_color_material_light">#ff999999</color>
+ <color name="datepicker_default_normal_text_color_material_dark">@android:color/white</color>
+
+ <color name="datepicker_default_disabled_text_color_material_light">#80999999</color>
+ <color name="datepicker_default_disabled_text_color_material_dark">#80999999</color>
+
+ <color name="datepicker_default_selected_text_color_material_light">#33b5e5</color>
+ <color name="datepicker_default_selected_text_color_material_dark">#33b5e5</color>
+
+ <color name="datepicker_default_pressed_text_color_material_light">#0099cc</color>
+ <color name="datepicker_default_pressed_text_color_material_dark">#0099cc</color>
+
+ <color name="datepicker_default_circle_background_color_material_light">@android:color/holo_blue_light</color>
+ <color name="datepicker_default_circle_background_color_material_dark">@android:color/holo_blue_light</color>
+
+ <color name="datepicker_default_view_animator_color_material_light">#f2f2f2</color>
+ <color name="datepicker_default_view_animator_color_material_dark">#ff303030</color>
+
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 53d9cee..0954ddf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -564,6 +564,12 @@
<item>5</item>
</integer-array>
+ <!-- Vibrator pattern for feedback when selecting a day/month/year date of a Calendar -->
+ <integer-array name="config_calendarDateVibePattern">
+ <item>125</item>
+ <item>5</item>
+ </integer-array>
+
<!-- Vibrator pattern for feedback about booting with safe mode disabled -->
<integer-array name="config_safeModeDisabledVibePattern">
<item>0</item>
@@ -1513,6 +1519,7 @@
See {@link com.android.server.notification.NotificationSignalExtractor} -->
<string-array name="config_notificationSignalExtractors">
<item>com.android.server.notification.ValidateNotificationPeople</item>
+ <item>com.android.server.notification.PackagePriorityExtractor</item>
<item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
</string-array>
@@ -1578,6 +1585,9 @@
<!-- Package name for default network scorer app; overridden by product overlays. -->
<string name="config_defaultNetworkScorerPackageName"></string>
+ <!-- default device has recents property -->
+ <bool name="config_hasRecents">true</bool>
+
<!-- Defines the default set of global actions. Actions may still be disabled or hidden based
on the current state of the device.
Each item must be one of the following strings:
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ad6c6cd..77b115b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -362,6 +362,24 @@
<dimen name="timepicker_minimum_margin_top_bottom">24dip</dimen>
<dimen name="timepicker_radial_picker_dimen">270dip</dimen>
+ <dimen name="datepicker_done_label_size">14sp</dimen>
+ <dimen name="datepicker_day_number_size">16sp</dimen>
+ <dimen name="datepicker_month_label_size">16sp</dimen>
+ <dimen name="datepicker_month_day_label_text_size">10sp</dimen>
+ <dimen name="datepicker_day_number_select_circle_radius">16dp</dimen>
+ <dimen name="datepicker_month_list_item_header_height">50dp</dimen>
+ <dimen name="datepicker_view_animator_height">270dp</dimen>
+ <dimen name="datepicker_year_picker_padding_top">8dp</dimen>
+ <dimen name="datepicker_year_label_height">64dp</dimen>
+ <dimen name="datepicker_year_label_text_size">22dp</dimen>
+ <dimen name="datepicker_component_width">270dp</dimen>
+ <dimen name="datepicker_selected_calendar_layout_height">155dp</dimen>
+ <dimen name="datepicker_selected_date_day_size">75dp</dimen>
+ <dimen name="datepicker_selected_date_month_size">30dp</dimen>
+ <dimen name="datepicker_selected_date_year_size">30dp</dimen>
+ <dimen name="datepicker_header_height">30dp</dimen>
+ <dimen name="datepicker_header_text_size">14dp</dimen>
+
<!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
<dimen name="immersive_mode_cling_width">-1px</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a0ca06e..d88ff12 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2233,6 +2233,21 @@
<public type="attr" name="buttonBarNegativeButtonStyle" />
<public type="attr" name="popupElevation" />
<public type="attr" name="actionBarPopupTheme" />
+ <public type="attr" name="multiArch" />
+ <public type="attr" name="touchscreenBlocksFocus" />
+ <public type="attr" name="windowElevation" />
+ <public type="attr" name="launchTaskBehindBackgroundAnimation" />
+ <public type="attr" name="launchTaskBehindSourceAnimation" />
+
+ <public type="attr" name="dateSelectorDayOfWeekBackgroundColor" />
+ <public type="attr" name="dateSelectorDayOfWeekTextAppearance" />
+ <public type="attr" name="dateSelectorBackgroundColor" />
+ <public type="attr" name="dateSelectorMonthTextAppearance" />
+ <public type="attr" name="dateSelectorDayOfMonthTextAppearance" />
+ <public type="attr" name="dateSelectorYearTextAppearance" />
+ <public type="attr" name="dateSelectorYearListItemTextAppearance" />
+ <public type="attr" name="dateSelectorYearListSelectedCircleColor" />
+ <public type="attr" name="calendarTextColor" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
@@ -2480,6 +2495,10 @@
<public-padding type="interpolator" name="l_resource_pad" end="0x010c0010" />
+ <!-- @SystemApi -->
+ <!-- @hide -->
+ <public type="style" name="Theme.Leanback.FormWizard"/>
+
<!-- An interpolator which accelerates fast but decelerates slowly. -->
<public type="interpolator" name="fast_out_slow_in" />
<!-- An interpolator which starts with a peak non-zero velocity and decelerates slowly. -->
@@ -2509,6 +2528,4 @@
<!-- A transition that moves views in or out of the scene to or from the left edge when
a view visibility changes. -->
<public type="transition" name="slide_left"/>
- <public type="attr" name="multiArch" />
- <public type="attr" name="touchscreenBlocksFocus" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 193d3c0..2ecaa5c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3627,13 +3627,13 @@
<!-- See SIM_REMOVED_DIALOG. This is the title of that dialog. -->
<string name="sim_removed_title">SIM card removed</string>
<!-- See SIM_REMOVED_DIALOG. This is the message of that dialog. -->
- <string name="sim_removed_message">The mobile network will be unavailable until you restart with a valid SIM card inserted.</string>
+ <string name="sim_removed_message">The cellular network will be unavailable until you restart with a valid SIM card inserted.</string>
<!-- See SIM_REMOVED_DIALOG. This is the button of that dialog. -->
<string name="sim_done_button">Done</string>
<!-- See SIM_ADDED_DIALOG. This is the title of that dialog. -->
<string name="sim_added_title">SIM card added</string>
<!-- See SIM_ADDED_DIALOG. This is the message of that dialog. -->
- <string name="sim_added_message">Restart your device to access the mobile network.</string>
+ <string name="sim_added_message">Restart your device to access the cellular network.</string>
<!-- See SIM_ADDED_DIALOG. This is the button of that dialog. -->
<string name="sim_restart_button">Restart</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 0eceae6..94ac19c 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -82,6 +82,8 @@
<item name="activityCloseExitAnimation">@anim/activity_close_exit</item>
<item name="taskOpenEnterAnimation">@anim/task_open_enter</item>
<item name="taskOpenExitAnimation">@anim/task_open_exit</item>
+ <item name="launchTaskBehindBackgroundAnimation">@anim/launch_task_behind_background</item>
+ <item name="launchTaskBehindSourceAnimation">@anim/launch_task_behind_source</item>
<item name="taskCloseEnterAnimation">@anim/task_close_enter</item>
<item name="taskCloseExitAnimation">@anim/task_close_exit</item>
<item name="taskToFrontEnterAnimation">@anim/task_open_enter</item>
@@ -570,7 +572,7 @@
</style>
<style name="Widget.DatePicker">
- <item name="internalLayout">@layout/date_picker</item>
+ <item name="legacyLayout">@android:layout/date_picker_legacy</item>
<item name="calendarViewShown">false</item>
</style>
@@ -824,15 +826,9 @@
<item name="textColorLink">?textColorLinkInverse</item>
</style>
- <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme">
- </style>
+ <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme" />
- <style name="TextAppearance.Theme.Dialog.AppError">
- <item name="textColor">#ffffc0c0</item>
- </style>
-
- <style name="TextAppearance.Widget">
- </style>
+ <style name="TextAppearance.Widget" />
<style name="TextAppearance.Widget.Button" parent="TextAppearance.Small.Inverse">
<item name="textColor">@color/primary_text_light_nodisable</item>
@@ -1206,7 +1202,6 @@
<item name="navigationButtonStyle">@style/Widget.Toolbar.Button.Navigation</item>
<item name="collapseIcon">?attr/homeAsUpIndicator</item>
<item name="contentInsetStart">16dp</item>
- <item name="touchscreenBlocksFocus">true</item>
</style>
<style name="Widget.Toolbar.Button.Navigation" parent="Widget">
@@ -1342,11 +1337,6 @@
<style name="TextAppearance.TimePicker.AmPmLabel" parent="TextAppearance">
</style>
- <style name="TextAppearance.Holo.TimePicker.TimeLabel" parent="TextAppearance.Holo">
- <item name="textSize">@dimen/timepicker_time_label_size</item>
- <item name="textColor">@color/timepicker_default_text_color_holo_dark</item>
- </style>
-
<style name="Widget.FastScroll">
<item name="thumbDrawable">?attr/fastScrollThumbDrawable</item>
<item name="trackDrawable">?attr/fastScrollTrackDrawable</item>
@@ -1366,8 +1356,26 @@
<item name="lightY">-200dp</item>
<item name="lightZ">800dp</item>
<item name="lightRadius">800dp</item>
- <item name="ambientShadowAlpha">0.125</item>
- <item name="spotShadowAlpha">0.25</item>
+ <item name="ambientShadowAlpha">0.0471</item>
+ <item name="spotShadowAlpha">0.1765</item>
+ </style>
+
+ <style name="TextAppearance.DatePicker.DayOfWeekLabel" parent="TextAppearance">
+ </style>
+
+ <style name="TextAppearance.DatePicker.Selector" parent="TextAppearance">
+ </style>
+
+ <style name="TextAppearance.DatePicker.Selector.MonthLabel" parent="TextAppearance.DatePicker.Selector">
+ </style>
+
+ <style name="TextAppearance.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.DatePicker.Selector">
+ </style>
+
+ <style name="TextAppearance.DatePicker.Selector.YearLabel" parent="TextAppearance.DatePicker.Selector">
+ </style>
+
+ <style name="TextAppearance.DatePicker.List.YearLabel" parent="TextAppearance">
</style>
</resources>
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 84d38ce..9b5629b 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -280,4 +280,29 @@
<style name="DeviceDefault.Light.ButtonBar" parent="Widget.Material.Light.ButtonBar"/>
<style name="DeviceDefault.Light.ButtonBar.AlertDialog" parent="Widget.Material.Light.ButtonBar.AlertDialog"/>
<style name="DeviceDefault.Light.SegmentedButton" parent="Widget.Material.Light.SegmentedButton"/>
+
+ <style name="TextAppearance.DeviceDefault.Light.TimePicker.TimeLabel" parent="TextAppearance.Material.TimePicker.TimeLabel"/>
+ <style name="TextAppearance.DeviceDefault.Light.TimePicker.AmPmLabel" parent="TextAppearance.Material.TimePicker.AmPmLabel"/>
+
+ <style name="Theme.DeviceDefault.Dialog.TimePicker" parent="Theme.Material.Dialog.TimePicker"/>
+ <style name="Theme.DeviceDefault.Light.Dialog.TimePicker" parent="Theme.Material.Light.Dialog.TimePicker"/>
+
+ <style name="TextAppearance.DeviceDefault.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material.DatePicker.DayOfWeekLabel"/>
+ <style name="TextAppearance.DeviceDefault.Light.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material.Light.DatePicker.DayOfWeekLabel"/>
+
+ <style name="TextAppearance.DeviceDefault.DatePicker.Selector" parent="TextAppearance.Material.DatePicker.Selector"/>
+ <style name="TextAppearance.DeviceDefault.Light.DatePicker.Selector" parent="TextAppearance.Material.Light.DatePicker.Selector"/>
+
+ <style name="TextAppearance.DeviceDefault.DatePicker.Selector.MonthLabel" parent="TextAppearance.Material.DatePicker.Selector.MonthLabel"/>
+ <style name="TextAppearance.DeviceDefault.Light.DatePicker.Selector.MonthLabel" parent="TextAppearance.Material.Light.DatePicker.Selector.MonthLabel"/>
+
+ <style name="TextAppearance.DeviceDefault.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Material.DatePicker.Selector.DayOfMonthLabel"/>
+ <style name="TextAppearance.DeviceDefault.Light.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Material.Light.DatePicker.Selector.DayOfMonthLabel"/>
+
+ <style name="TextAppearance.DeviceDefault.DatePicker.Selector.YearLabel" parent="TextAppearance.Material.DatePicker.Selector.YearLabel"/>
+ <style name="TextAppearance.DeviceDefault.Light.DatePicker.Selector.YearLabel" parent="TextAppearance.Material.Light.DatePicker.Selector.YearLabel"/>
+
+ <style name="Theme.DeviceDefault.Dialog.DatePicker" parent="Theme.Material.Dialog.DatePicker"/>
+ <style name="Theme.DeviceDefault.Light.Dialog.DatePicker" parent="Theme.Material.Light.Dialog.DatePicker"/>
+
</resources>
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index 327d6b5..5dfbaed 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -477,8 +477,18 @@
</style>
<style name="Widget.Holo.DatePicker" parent="Widget.DatePicker">
+ <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
<item name="internalLayout">@layout/date_picker_holo</item>
<item name="calendarViewShown">true</item>
+ <item name="dateSelectorDayOfWeekBackgroundColor">?attr/datePickerHeaderDayOfWeekLabelBackgroundColor</item>
+ <item name="dateSelectorDayOfWeekTextAppearance">?attr/datePickerHeaderDayOfWeekLabelTextAppearance</item>
+ <item name="dateSelectorBackgroundColor">?attr/datePickerHeaderSelectorBackgroundColor</item>
+ <item name="dateSelectorMonthTextAppearance">?attr/datePickerHeaderSelectorMonthLabelTextAppearance</item>
+ <item name="dateSelectorDayOfMonthTextAppearance">?attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance</item>
+ <item name="dateSelectorYearTextAppearance">?attr/datePickerHeaderSelectorYearLabelTextAppearance</item>
+ <item name="dateSelectorYearListItemTextAppearance">?attr/datePickerHeaderListYearLabelTextAppearance</item>
+ <item name="dateSelectorYearListSelectedCircleColor">?attr/datePickerHeaderListYearLabelCircleBackgroundColor</item>
+ <item name="calendarTextColor">@color/date_picker_calendar_holo_dark</item>
</style>
<style name="Widget.Holo.ActivityChooserView" parent="Widget.ActivityChooserView" />
@@ -884,7 +894,20 @@
<item name="numbersSelectorColor">@color/holo_blue_light</item>
</style>
- <style name="Widget.Holo.Light.DatePicker" parent="Widget.Holo.DatePicker" />
+ <style name="Widget.Holo.Light.DatePicker" parent="Widget.DatePicker">
+ <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
+ <item name="internalLayout">@layout/date_picker_holo</item>
+ <item name="calendarViewShown">true</item>
+ <item name="dateSelectorDayOfWeekBackgroundColor">?attr/datePickerHeaderDayOfWeekLabelBackgroundColor</item>
+ <item name="dateSelectorDayOfWeekTextAppearance">?attr/datePickerHeaderDayOfWeekLabelTextAppearance</item>
+ <item name="dateSelectorBackgroundColor">?attr/datePickerHeaderSelectorBackgroundColor</item>
+ <item name="dateSelectorMonthTextAppearance">?attr/datePickerHeaderSelectorMonthLabelTextAppearance</item>
+ <item name="dateSelectorDayOfMonthTextAppearance">?attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance</item>
+ <item name="dateSelectorYearTextAppearance">?attr/datePickerHeaderSelectorYearLabelTextAppearance</item>
+ <item name="dateSelectorYearListItemTextAppearance">?attr/datePickerHeaderListYearLabelTextAppearance</item>
+ <item name="dateSelectorYearListSelectedCircleColor">?attr/datePickerHeaderListYearLabelCircleBackgroundColor</item>
+ <item name="calendarTextColor">@color/date_picker_calendar_holo_light</item>
+ </style>
<style name="Widget.Holo.Light.ActivityChooserView" parent="Widget.Holo.ActivityChooserView">
<item name="background">@drawable/ab_share_pack_holo_light</item>
@@ -1183,6 +1206,11 @@
<item name="externalRouteEnabledDrawable">@drawable/ic_media_route_holo_light</item>
</style>
+ <style name="TextAppearance.Holo.TimePicker.TimeLabel" parent="TextAppearance.Holo">
+ <item name="textSize">@dimen/timepicker_time_label_size</item>
+ <item name="textColor">@color/timepicker_default_text_color_holo_dark</item>
+ </style>
+
<style name="TextAppearance.Holo.TimePicker.AmPmLabel" parent="TextAppearance.Holo">
<item name="textSize">@dimen/timepicker_ampm_label_size</item>
<item name="textAllCaps">true</item>
@@ -1202,6 +1230,62 @@
<item name="textStyle">bold</item>
</style>
+ <style name="TextAppearance.Holo.DatePicker.DayOfWeekLabel" parent="TextAppearance.Holo">
+ <item name="includeFontPadding">false</item>
+ <item name="textColor">@color/black</item>
+ <item name="textSize">@dimen/datepicker_header_text_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.DatePicker.Selector" parent="TextAppearance.Holo">
+ <item name="includeFontPadding">false</item>
+ <item name="textColor">@color/date_picker_selector_holo_dark</item>
+ </style>
+
+ <style name="TextAppearance.Holo.DatePicker.Selector.MonthLabel" parent="TextAppearance.Holo.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Holo.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.DatePicker.Selector.YearLabel" parent="TextAppearance.Holo.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_year_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.DatePicker.List.YearLabel" parent="TextAppearance.Holo">
+ <item name="textColor">@color/date_picker_year_selector_holo_dark</item>
+ <item name="textSize">@dimen/datepicker_year_label_text_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.Light.DatePicker.DayOfWeekLabel" parent="TextAppearance.Holo">
+ <item name="includeFontPadding">false</item>
+ <item name="textColor">@color/white</item>
+ <item name="textSize">@dimen/datepicker_header_text_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.Light.DatePicker.Selector" parent="TextAppearance.Holo">
+ <item name="includeFontPadding">false</item>
+ <item name="textColor">@color/date_picker_selector_holo_light</item>
+ </style>
+
+ <style name="TextAppearance.Holo.Light.DatePicker.Selector.MonthLabel" parent="TextAppearance.Holo.Light.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.Light.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Holo.Light.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.Light.DatePicker.Selector.YearLabel" parent="TextAppearance.Holo.Light.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_year_size</item>
+ </style>
+
+ <style name="TextAppearance.Holo.Light.DatePicker.List.YearLabel" parent="TextAppearance.Holo">
+ <item name="textColor">@color/date_picker_year_selector_holo_light</item>
+ <item name="textSize">@dimen/datepicker_year_label_text_size</item>
+ </style>
+
<style name="Widget.Holo.FastScroll" parent="Widget.FastScroll">
<item name="thumbMinWidth">0dp</item>
<item name="thumbMinHeight">0dp</item>
diff --git a/core/res/res/values/styles_leanback.xml b/core/res/res/values/styles_leanback.xml
index 6f9a88e..5531433 100644
--- a/core/res/res/values/styles_leanback.xml
+++ b/core/res/res/values/styles_leanback.xml
@@ -30,4 +30,30 @@
<item name="hideWheelUntilFocused">true</item>
</style>
+ <!-- Setup and form wizard themes -->
+ <style name="TextAppearance.Leanback.FormWizard" parent="@style/TextAppearance.Material">
+ <item name="textSize">18sp</item>
+ <item name="fontFamily">sans-serif-light</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.FormWizard.Small" parent="@style/TextAppearance.Material.Small">
+ <item name="textSize">18sp</item>
+ <item name="fontFamily">sans-serif-light</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.FormWizard.Medium" parent="@style/TextAppearance.Material.Medium">
+ <item name="textSize">36sp</item>
+ <item name="fontFamily">sans-serif-thin</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.FormWizard.Large" parent="@style/TextAppearance.Material.Large">
+ <item name="textSize">56sp</item>
+ <item name="fontFamily">sans-serif-thin</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.FormWizard.ListItem" parent="@style/TextAppearance.Material.Subhead">
+ <item name="textSize">18sp</item>
+ <item name="fontFamily">sans-serif-condensed</item>
+ </style>
+
</resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 4623258..1d07c8d 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -353,6 +353,62 @@
<item name="textStyle">bold</item>
</style>
+ <style name="TextAppearance.Material.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material">
+ <item name="includeFontPadding">false</item>
+ <item name="textColor">@color/black</item>
+ <item name="textSize">@dimen/datepicker_header_text_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.DatePicker.Selector" parent="TextAppearance.Material">
+ <item name="includeFontPadding">false</item>
+ <item name="textColor">@color/date_picker_selector_material_dark</item>
+ </style>
+
+ <style name="TextAppearance.Material.DatePicker.Selector.MonthLabel" parent="TextAppearance.Material.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Material.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.DatePicker.Selector.YearLabel" parent="TextAppearance.Material.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_year_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.DatePicker.List.YearLabel" parent="TextAppearance.Material">
+ <item name="textColor">@color/date_picker_year_selector_material_dark</item>
+ <item name="textSize">@dimen/datepicker_year_label_text_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.Light.DatePicker.DayOfWeekLabel" parent="TextAppearance.Material">
+ <item name="includeFontPadding">false</item>
+ <item name="textColor">@color/white</item>
+ <item name="textSize">@dimen/datepicker_header_text_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.Light.DatePicker.Selector" parent="TextAppearance.Material">
+ <item name="includeFontPadding">false</item>
+ <item name="textColor">@color/date_picker_selector_material_light</item>
+ </style>
+
+ <style name="TextAppearance.Material.Light.DatePicker.Selector.MonthLabel" parent="TextAppearance.Material.Light.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_month_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.Light.DatePicker.Selector.DayOfMonthLabel" parent="TextAppearance.Material.Light.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_day_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.Light.DatePicker.Selector.YearLabel" parent="TextAppearance.Material.Light.DatePicker.Selector">
+ <item name="textSize">@dimen/datepicker_selected_date_year_size</item>
+ </style>
+
+ <style name="TextAppearance.Material.Light.DatePicker.List.YearLabel" parent="TextAppearance.Material">
+ <item name="textColor">@color/date_picker_year_selector_material_light</item>
+ <item name="textSize">@dimen/datepicker_year_label_text_size</item>
+ </style>
+
<style name="TextAppearance.StatusBar.Material" />
<style name="TextAppearance.StatusBar.Material.EventContent">
@@ -575,8 +631,18 @@
</style>
<style name="Widget.Material.DatePicker" parent="Widget.DatePicker">
+ <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
<item name="internalLayout">@layout/date_picker_holo</item>
<item name="calendarViewShown">true</item>
+ <item name="dateSelectorDayOfWeekBackgroundColor">?attr/datePickerHeaderDayOfWeekLabelBackgroundColor</item>
+ <item name="dateSelectorDayOfWeekTextAppearance">?attr/datePickerHeaderDayOfWeekLabelTextAppearance</item>
+ <item name="dateSelectorBackgroundColor">?attr/datePickerHeaderSelectorBackgroundColor</item>
+ <item name="dateSelectorMonthTextAppearance">?attr/datePickerHeaderSelectorMonthLabelTextAppearance</item>
+ <item name="dateSelectorDayOfMonthTextAppearance">?attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance</item>
+ <item name="dateSelectorYearTextAppearance">?attr/datePickerHeaderSelectorYearLabelTextAppearance</item>
+ <item name="dateSelectorYearListItemTextAppearance">?attr/datePickerHeaderListYearLabelTextAppearance</item>
+ <item name="dateSelectorYearListSelectedCircleColor">?attr/datePickerHeaderListYearLabelCircleBackgroundColor</item>
+ <item name="calendarTextColor">@color/date_picker_calendar_holo_dark</item>
</style>
<style name="Widget.Material.ActivityChooserView" parent="Widget.ActivityChooserView">
@@ -897,7 +963,21 @@
<item name="disabledColor">@color/bright_foreground_disabled_material_light</item>
</style>
- <style name="Widget.Material.Light.DatePicker" parent="Widget.Material.DatePicker"/>
+ <style name="Widget.Material.Light.DatePicker" parent="Widget.DatePicker">
+ <item name="legacyLayout">@layout/date_picker_legacy_holo</item>
+ <item name="internalLayout">@layout/date_picker_holo</item>
+ <item name="calendarViewShown">true</item>
+ <item name="dateSelectorDayOfWeekBackgroundColor">?attr/datePickerHeaderDayOfWeekLabelBackgroundColor</item>
+ <item name="dateSelectorDayOfWeekTextAppearance">?attr/datePickerHeaderDayOfWeekLabelTextAppearance</item>
+ <item name="dateSelectorBackgroundColor">?attr/datePickerHeaderSelectorBackgroundColor</item>
+ <item name="dateSelectorMonthTextAppearance">?attr/datePickerHeaderSelectorMonthLabelTextAppearance</item>
+ <item name="dateSelectorDayOfMonthTextAppearance">?attr/datePickerHeaderSelectorDayOfMonthLabelTextAppearance</item>
+ <item name="dateSelectorYearTextAppearance">?attr/datePickerHeaderSelectorYearLabelTextAppearance</item>
+ <item name="dateSelectorYearListItemTextAppearance">?attr/datePickerHeaderListYearLabelTextAppearance</item>
+ <item name="dateSelectorYearListSelectedCircleColor">?attr/datePickerHeaderListYearLabelCircleBackgroundColor</item>
+ <item name="calendarTextColor">@color/date_picker_calendar_holo_light</item>
+ </style>
+
<style name="Widget.Material.Light.ActivityChooserView" parent="Widget.Material.ActivityChooserView" />
<style name="Widget.Material.Light.ImageWell" parent="Widget.Material.ImageWell"/>
<style name="Widget.Material.Light.ListView" parent="Widget.Material.ListView"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b22155c..4b4e633 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -289,6 +289,7 @@
<java-symbol type="bool" name="config_enableMultiUserUI"/>
<java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
<java-symbol type="bool" name="config_windowIsRound" />
+ <java-symbol type="bool" name="config_hasRecents" />
<java-symbol type="integer" name="config_bluetooth_max_advertisers" />
<java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
@@ -1181,7 +1182,7 @@
<java-symbol type="layout" name="calendar_view" />
<java-symbol type="layout" name="character_picker" />
<java-symbol type="layout" name="character_picker_button" />
- <java-symbol type="layout" name="date_picker" />
+ <java-symbol type="layout" name="date_picker_legacy" />
<java-symbol type="layout" name="date_picker_dialog" />
<java-symbol type="layout" name="expanded_menu_layout" />
<java-symbol type="layout" name="fragment_bread_crumb_item" />
@@ -1839,9 +1840,10 @@
<java-symbol type="dimen" name="subtitle_shadow_offset" />
<java-symbol type="dimen" name="subtitle_outline_width" />
- <!-- From the new TimePicker -->
- <java-symbol type="attr" name="timePickerHeaderBackgroundColor" />
+ <!-- From the new TimePicker and DatePicker -->
<java-symbol type="attr" name="timePickerDialogTheme" />
+ <java-symbol type="attr" name="timePickerHeaderTimeLabelTextAppearance" />
+ <java-symbol type="attr" name="timePickerHeaderBackgroundColor" />
<java-symbol type="attr" name="headerSelectedTextColor" />
<java-symbol type="attr" name="headerUnselectedTextColor" />
<java-symbol type="attr" name="numbersTextColor" />
@@ -1852,9 +1854,18 @@
<java-symbol type="attr" name="numbersSelectorColor" />
<java-symbol type="attr" name="timePickerHeaderTimeLabelTextAppearance" />
<java-symbol type="attr" name="nestedScrollingEnabled" />
+ <java-symbol type="attr" name="datePickerDialogTheme" />
+ <java-symbol type="attr" name="datePickerHeaderSelectorBackgroundColor" />
+ <java-symbol type="attr" name="datePickerHeaderListYearLabelCircleBackgroundColor" />
+ <java-symbol type="attr" name="calendarTextColor" />
+
<java-symbol type="style" name="TextAppearance.Holo.TimePicker.TimeLabel" />
+
<java-symbol type="layout" name="time_picker_holo" />
<java-symbol type="layout" name="time_header_label" />
+ <java-symbol type="layout" name="year_label_text_view" />
+ <java-symbol type="layout" name="date_picker_holo" />
+
<java-symbol type="id" name="time_header" />
<java-symbol type="id" name="hours" />
<java-symbol type="id" name="minutes" />
@@ -1863,6 +1874,15 @@
<java-symbol type="id" name="separator" />
<java-symbol type="id" name="layout_buttons" />
<java-symbol type="id" name="done_button" />
+ <java-symbol type="id" name="date_picker_header" />
+ <java-symbol type="id" name="date_picker_month_and_day_layout" />
+ <java-symbol type="id" name="day_picker_selector_layout" />
+ <java-symbol type="id" name="date_picker_month" />
+ <java-symbol type="id" name="date_picker_day" />
+ <java-symbol type="id" name="date_picker_year" />
+ <java-symbol type="id" name="animator" />
+ <java-symbol type="id" name="done" />
+
<java-symbol type="string" name="done_label" />
<java-symbol type="string" name="hour_picker_description" />
<java-symbol type="string" name="minute_picker_description" />
@@ -1884,7 +1904,42 @@
<java-symbol type="string" name="timepicker_numbers_radius_multiplier_normal" />
<java-symbol type="string" name="timepicker_transition_mid_radius_multiplier" />
<java-symbol type="string" name="timepicker_transition_end_radius_multiplier" />
+
+ <java-symbol type="string" name="item_is_selected" />
+ <java-symbol type="string" name="day_of_week_label_typeface" />
+ <java-symbol type="string" name="select_day" />
+ <java-symbol type="string" name="day_picker_description" />
+ <java-symbol type="string" name="select_year" />
+ <java-symbol type="string" name="year_picker_description" />
+
+ <java-symbol type="dimen" name="datepicker_day_number_size" />
+ <java-symbol type="dimen" name="datepicker_month_label_size" />
+ <java-symbol type="dimen" name="datepicker_month_day_label_text_size" />
+ <java-symbol type="dimen" name="datepicker_month_list_item_header_height" />
+ <java-symbol type="dimen" name="datepicker_day_number_select_circle_radius" />
+ <java-symbol type="dimen" name="datepicker_view_animator_height" />
+ <java-symbol type="dimen" name="datepicker_year_label_height" />
+ <java-symbol type="dimen" name="datepicker_year_picker_padding_top" />
+
+ <java-symbol type="color" name="timepicker_default_text_color_holo_light" />
+ <java-symbol type="color" name="timepicker_default_disabled_color_holo_light" />
+ <java-symbol type="color" name="timepicker_default_ampm_unselected_background_color_holo_light" />
+ <java-symbol type="color" name="timepicker_default_ampm_selected_background_color_holo_light" />
+
+ <java-symbol type="color" name="datepicker_default_normal_text_color_holo_light" />
+ <java-symbol type="color" name="datepicker_default_disabled_text_color_holo_light" />
+ <java-symbol type="color" name="datepicker_default_circle_background_color_holo_light" />
+ <java-symbol type="color" name="datepicker_default_header_dayofweek_background_color_holo_light" />
+ <java-symbol type="color" name="datepicker_default_header_selector_background_holo_light" />
+
+ <java-symbol type="color" name="datepicker_default_normal_text_color_material_light" />
+ <java-symbol type="color" name="datepicker_default_disabled_text_color_material_light" />
+ <java-symbol type="color" name="datepicker_default_circle_background_color_material_light" />
+ <java-symbol type="color" name="datepicker_default_header_dayofweek_background_color_material_light" />
+ <java-symbol type="color" name="datepicker_default_header_selector_background_material_light" />
+
<java-symbol type="array" name="config_clockTickVibePattern" />
+ <java-symbol type="array" name="config_calendarDateVibePattern" />
<!-- From various Material changes -->
<java-symbol type="attr" name="toolbarStyle" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 6ada975..0438ed1 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -409,7 +409,34 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.DatePicker</item>
- <item name="fastScrollThumbDrawable">@drawable/scrollbar_handle_accelerated_anim2</item>
+ <!-- DatePicker Header day of week label background color -->
+ <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/darker_gray</item>
+
+ <!-- DatePicker Header day of week label text appearance -->
+ <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.DatePicker.DayOfWeekLabel</item>
+
+ <!-- DatePicker Header selector background color -->
+ <item name="datePickerHeaderSelectorBackgroundColor">@android:color/darker_gray</item>
+
+ <!-- DatePicker Header selector month label text appearance -->
+ <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.DatePicker.Selector.MonthLabel</item>
+
+ <!-- DatePicker Header selector day of month label text appearance -->
+ <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.DatePicker.Selector.DayOfMonthLabel</item>
+
+ <!-- DatePicker Header selector year label text appearance -->
+ <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.DatePicker.Selector.YearLabel</item>
+
+ <!-- DatePicker list year label text appearance -->
+ <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.DatePicker.List.YearLabel</item>
+
+ <!-- DatePicker list year label circle background color -->
+ <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/darker_gray</item>
+
+ <!-- DatePicker dialog theme -->
+ <item name="datePickerDialogTheme">@android:style/Theme.Dialog.DatePicker</item>
+
+ <item name="fastScrollThumbDrawable">@android:drawable/scrollbar_handle_accelerated_anim2</item>
<item name="fastScrollTrackDrawable">@null</item>
<item name="fastScrollPreviewBackgroundRight">@drawable/menu_submenu_background</item>
<item name="fastScrollPreviewBackgroundLeft">@drawable/menu_submenu_background</item>
@@ -731,6 +758,14 @@
<item name="windowContentOverlay">@null</item>
</style>
+ <!-- Default heme for the DatePicker dialog windows, which is used by the
+ {@link android.app.DatePickerDialog} class. -->
+ <style name="Theme.Dialog.DatePicker">
+ <item name="windowBackground">@android:color/transparent</item>
+ <item name="windowTitleStyle">@android:style/DialogWindowTitle</item>
+ <item name="windowContentOverlay">@null</item>
+ </style>
+
<!-- Default dark theme for panel windows (on API level 10 and lower). This removes all
extraneous window decorations, so you basically have an empty rectangle in which
to place your content. It makes the window floating, with a transparent
@@ -820,21 +855,14 @@
<eat-comment />
<!-- Theme for the dialog shown when an app crashes or ANRs. -->
- <style name="Theme.Dialog.AppError" parent="Theme.DeviceDefault.Light.Dialog">
- <item name="windowFrame">@null</item>
- <item name="windowTitleStyle">@style/DialogWindowTitle</item>
- <item name="windowBackground">@color/transparent</item>
- <item name="windowIsFloating">true</item>
- <item name="windowContentOverlay">@null</item>
+ <style name="Theme.Dialog.AppError" parent="Theme.DeviceDefault.Light.Dialog.Alert">
<item name="windowContentTransitions">false</item>
- <item name="textAppearance">@style/TextAppearance.Theme.Dialog.AppError</item>
<item name="windowCloseOnTouchOutside">false</item>
</style>
<!-- Special theme for the recent apps dialog, to allow customization
with overlays. -->
<style name="Theme.Dialog.RecentApplications" parent="Theme.DeviceDefault.Light.Dialog">
- <item name="windowFrame">@null</item>
<item name="windowBackground">@color/transparent</item>
<item name="windowAnimationStyle">@style/Animation.RecentApplications</item>
<item name="textColor">@color/secondary_text_nofocus</item>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index ee2c7df..2febbef 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -207,7 +207,23 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.DeviceDefault.DatePicker</item>
+ <!-- The DatePicker Header day of week label text appearance -->
+ <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.DeviceDefault.DatePicker.DayOfWeekLabel</item>
+
+ <!-- The DatePicker Header selector month label text appearance -->
+ <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.DeviceDefault.DatePicker.Selector.MonthLabel</item>
+
+ <!-- The DatePicker Header selector day of month label text appearance -->
+ <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.DeviceDefault.DatePicker.Selector.DayOfMonthLabel</item>
+
+ <!-- The DatePicker Header selector year label text appearance -->
+ <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.DeviceDefault.DatePicker.Selector.YearLabel</item>
+
+ <!-- DatePicker dialog theme -->
+ <item name="datePickerDialogTheme">@android:style/Theme.DeviceDefault.Dialog.DatePicker</item>
+
<item name="mediaRouteButtonStyle">@style/Widget.DeviceDefault.MediaRouteButton</item>
+
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with no action bar -->
@@ -466,7 +482,23 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.DeviceDefault.Light.DatePicker</item>
+ <!-- The DatePicker Header day of week label text appearance -->
+ <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.DeviceDefault.Light.DatePicker.DayOfWeekLabel</item>
+
+ <!-- The DatePicker Header selector month label text appearance -->
+ <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.DeviceDefault.Light.DatePicker.Selector.MonthLabel</item>
+
+ <!-- The DatePicker Header selector day of month label text appearance -->
+ <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.DeviceDefault.Light.DatePicker.Selector.DayOfMonthLabel</item>
+
+ <!-- The DatePicker Header selector year label text appearance -->
+ <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.DeviceDefault.Light.DatePicker.Selector.YearLabel</item>
+
+ <!-- DatePicker dialog theme -->
+ <item name="datePickerDialogTheme">@android:style/Theme.DeviceDefault.Light.Dialog.DatePicker</item>
+
<item name="mediaRouteButtonStyle">@style/Widget.DeviceDefault.Light.MediaRouteButton</item>
+
</style>
<!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index 76bfc4b..dda42c1 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -389,6 +389,33 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.Holo.DatePicker</item>
+ <!-- DatePicker Header day of week label background color -->
+ <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/datepicker_default_header_dayofweek_background_color_holo_dark</item>
+
+ <!-- DatePicker Header day of week label text appearance -->
+ <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.DayOfWeekLabel</item>
+
+ <!-- DatePicker Header selector background color -->
+ <item name="datePickerHeaderSelectorBackgroundColor">@android:color/datepicker_default_header_selector_background_holo_dark</item>
+
+ <!-- DatePicker Header selector month label text appearance -->
+ <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.Selector.MonthLabel</item>
+
+ <!-- DatePicker Header selector day of month label text appearance -->
+ <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.Selector.DayOfMonthLabel</item>
+
+ <!-- DatePicker Header selector year label text appearance -->
+ <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.Selector.YearLabel</item>
+
+ <!-- DatePicker list year label text appearance -->
+ <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.Holo.DatePicker.List.YearLabel</item>
+
+ <!-- DatePicker list year label circle background color -->
+ <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/datepicker_default_circle_background_color_holo_dark</item>
+
+ <!-- DatePicker dialog theme -->
+ <item name="datePickerDialogTheme">@android:style/Theme.Holo.Dialog.DatePicker</item>
+
<item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_holo</item>
<item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item>
<item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item>
@@ -728,6 +755,33 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.Holo.Light.DatePicker</item>
+ <!-- DatePicker Header day of week label background color -->
+ <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/datepicker_default_header_dayofweek_background_color_holo_light</item>
+
+ <!-- DatePicker Header day of week label text appearance -->
+ <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.DayOfWeekLabel</item>
+
+ <!-- DatePicker Header selector background color -->
+ <item name="datePickerHeaderSelectorBackgroundColor">@android:color/datepicker_default_header_selector_background_holo_light</item>
+
+ <!-- DatePicker Header selector month label text appearance -->
+ <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.Selector.MonthLabel</item>
+
+ <!-- DatePicker Header selector day of month label text appearance -->
+ <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.Selector.DayOfMonthLabel</item>
+
+ <!-- DatePicker Header selector year label text appearance -->
+ <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.Selector.YearLabel</item>
+
+ <!-- DatePicker list year label text appearance -->
+ <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.Holo.Light.DatePicker.List.YearLabel</item>
+
+ <!-- DatePicker list year label circle background color -->
+ <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/datepicker_default_circle_background_color_holo_light</item>
+
+ <!-- DatePicker dialog theme -->
+ <item name="datePickerDialogTheme">@android:style/Theme.Holo.Light.Dialog.DatePicker</item>
+
<item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_holo</item>
<item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item>
<item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item>
@@ -944,6 +998,14 @@
{@link android.app.TimePickerDialog} class. -->
<style name="Theme.Holo.Dialog.TimePicker" parent="Theme.Holo.Dialog.Alert" />
+ <!-- Holo theme for the DatePicker dialog windows, which is used by the
+ {@link android.app.DatePickerDialog} class. -->
+ <style name="Theme.Holo.Dialog.DatePicker">
+ <item name="windowBackground">@color/transparent</item>
+ <item name="windowTitleStyle">@style/DialogWindowTitle.Holo</item>
+ <item name="windowContentOverlay">@null</item>
+ </style>
+
<!-- Theme for a window that will be displayed either full-screen on
smaller screens (small, normal) or as a dialog on larger screens
(large, xlarge). -->
@@ -1059,6 +1121,14 @@
{@link android.app.TimePickerDialog} class. -->
<style name="Theme.Holo.Light.Dialog.TimePicker" parent="Theme.Holo.Light.Dialog.Alert" />
+ <!-- Holo Light theme for the DatePicker dialog windows, which is used by the
+ {@link android.app.DatePickerDialog} class. -->
+ <style name="Theme.Holo.Light.Dialog.DatePicker">
+ <item name="windowBackground">@android:color/transparent</item>
+ <item name="windowTitleStyle">@android:style/DialogWindowTitle.Holo.Light</item>
+ <item name="windowContentOverlay">@null</item>
+ </style>
+
<!-- Theme for a presentation window on a secondary display. -->
<style name="Theme.Holo.Light.Dialog.Presentation" parent="Theme.Holo.Light.NoActionBar.Fullscreen" />
diff --git a/core/res/res/values/themes_leanback.xml b/core/res/res/values/themes_leanback.xml
index 6f6385b..3860eb1 100644
--- a/core/res/res/values/themes_leanback.xml
+++ b/core/res/res/values/themes_leanback.xml
@@ -62,4 +62,17 @@
<item name="windowCloseOnTouchOutside">false</item>
<item name="alertDialogStyle">@style/AlertDialog.Leanback</item>
</style>
+
+ <!-- Setup and form wizard themes -->
+
+ <style name="Theme.Leanback.FormWizard" parent="Theme.Material.NoActionBar">
+ <item name="windowBackground">@drawable/background_leanback_setup</item>
+ <item name="colorBackgroundCacheHint">@null</item>
+ <item name="windowShowWallpaper">false</item>
+ <item name="textAppearanceSmall">@style/TextAppearance.Leanback.FormWizard.Small</item>
+ <item name="textAppearanceMedium">@style/TextAppearance.Leanback.FormWizard.Medium</item>
+ <item name="textAppearanceLarge">@style/TextAppearance.Leanback.FormWizard.Large</item>
+ <item name="textAppearanceListItem">@style/TextAppearance.Leanback.FormWizard.ListItem</item>
+ <item name="textAppearance">@style/TextAppearance.Leanback.FormWizard</item>
+ </style>
</resources>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 47f3778..bdaeb45 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -367,6 +367,33 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.Material.DatePicker</item>
+ <!-- DatePicker Header day of week label background color -->
+ <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/datepicker_default_header_dayofweek_background_color_material_dark</item>
+
+ <!-- DatePicker Header day of week label text appearance -->
+ <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.Material.DatePicker.DayOfWeekLabel</item>
+
+ <!-- DatePicker Header selector background color -->
+ <item name="datePickerHeaderSelectorBackgroundColor">@android:color/datepicker_default_header_selector_background_material_dark</item>
+
+ <!-- DatePicker Header selector month label text appearance -->
+ <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.Material.DatePicker.Selector.MonthLabel</item>
+
+ <!-- DatePicker Header selector day of month label text appearance -->
+ <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.Material.DatePicker.Selector.DayOfMonthLabel</item>
+
+ <!-- DatePicker Header selector year label text appearance -->
+ <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.Material.DatePicker.Selector.YearLabel</item>
+
+ <!-- DatePicker list year label text appearance -->
+ <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.Material.DatePicker.List.YearLabel</item>
+
+ <!-- DatePicker list year label circle background color -->
+ <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/datepicker_default_circle_background_color_material_dark</item>
+
+ <!-- DatePicker dialog theme -->
+ <item name="datePickerDialogTheme">@android:style/Theme.Material.Dialog.DatePicker</item>
+
<!-- TODO: This belongs in a FastScroll style -->
<item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_material</item>
<item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item>
@@ -715,6 +742,33 @@
<!-- DatePicker style -->
<item name="datePickerStyle">@style/Widget.Material.Light.DatePicker</item>
+ <!-- DatePicker Header day of week label background color -->
+ <item name="datePickerHeaderDayOfWeekLabelBackgroundColor">@android:color/datepicker_default_header_dayofweek_background_color_material_light</item>
+
+ <!-- DatePicker Header day of week label text appearance -->
+ <item name="datePickerHeaderDayOfWeekLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.DayOfWeekLabel</item>
+
+ <!-- DatePicker Header selector background color -->
+ <item name="datePickerHeaderSelectorBackgroundColor">@android:color/datepicker_default_header_selector_background_material_light</item>
+
+ <!-- DatePicker Header selector month label text appearance -->
+ <item name="datePickerHeaderSelectorMonthLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.Selector.MonthLabel</item>
+
+ <!-- DatePicker Header selector day of month label text appearance -->
+ <item name="datePickerHeaderSelectorDayOfMonthLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.Selector.DayOfMonthLabel</item>
+
+ <!-- DatePicker Header selector year label text appearance -->
+ <item name="datePickerHeaderSelectorYearLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.Selector.YearLabel</item>
+
+ <!-- DatePicker list year label text appearance -->
+ <item name="datePickerHeaderListYearLabelTextAppearance">@style/TextAppearance.Material.Light.DatePicker.List.YearLabel</item>
+
+ <!-- DatePicker list year label circle background color -->
+ <item name="datePickerHeaderListYearLabelCircleBackgroundColor">@android:color/datepicker_default_circle_background_color_material_light</item>
+
+ <!-- DatePicker dialog theme -->
+ <item name="datePickerDialogTheme">@android:style/Theme.Material.Light.Dialog.DatePicker</item>
+
<item name="fastScrollThumbDrawable">@drawable/fastscroll_thumb_material</item>
<item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item>
<item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item>
@@ -1011,7 +1065,8 @@
<style name="Theme.Material.Dialog">
<item name="windowFrame">@null</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
- <item name="windowBackground">@drawable/dialog_background_shadow_material</item>
+ <item name="windowBackground">@drawable/dialog_background_material</item>
+ <item name="windowElevation">@dimen/floating_window_z</item>
<item name="windowIsFloating">true</item>
<item name="windowContentOverlay">@null</item>
<item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
@@ -1077,6 +1132,7 @@
its pixels. -->
<style name="Theme.Material.Dialog.NoFrame">
<item name="windowBackground">@color/transparent</item>
+ <item name="windowElevation">0dp</item>
<item name="windowAnimationStyle">@null</item>
<item name="backgroundDimEnabled">false</item>
<item name="windowIsTranslucent">true</item>
@@ -1085,7 +1141,6 @@
</style>
<style name="Theme.Material.Dialog.BaseAlert">
- <item name="windowBackground">@color/transparent</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
<item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
<item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
@@ -1100,6 +1155,7 @@
<style name="Theme.Material.Dialog.BaseTimePicker">
<item name="windowBackground">@color/transparent</item>
+ <item name="windowElevation">0dp</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
<item name="windowContentOverlay">@null</item>
</style>
@@ -1108,6 +1164,16 @@
{@link android.app.TimePickerDialog} class. -->
<style name="Theme.Material.Dialog.TimePicker" parent="Theme.Material.Dialog.BaseTimePicker"/>
+ <style name="Theme.Material.Dialog.BaseDatePicker">
+ <item name="windowBackground">@color/transparent</item>
+ <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item>
+ <item name="windowContentOverlay">@null</item>
+ </style>
+
+ <!-- Material theme for the DatePicker dialog windows, which is used by the
+ {@link android.app.DatePickerDialog} class. -->
+ <style name="Theme.Material.Dialog.DatePicker" parent="Theme.Material.Dialog.BaseDatePicker"/>
+
<!-- Theme for a window that will be displayed either full-screen on
smaller screens (small, normal) or as a dialog on larger screens
(large, xlarge). -->
@@ -1131,7 +1197,8 @@
<style name="Theme.Material.Light.Dialog">
<item name="windowFrame">@null</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
- <item name="windowBackground">@drawable/dialog_background_shadow_material</item>
+ <item name="windowBackground">@drawable/dialog_background_material</item>
+ <item name="windowElevation">@dimen/floating_window_z</item>
<item name="windowIsFloating">true</item>
<item name="windowContentOverlay">@null</item>
<item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
@@ -1203,7 +1270,6 @@
<style name="Theme.Material.Light.DialogWhenLarge.NoActionBar" parent="@style/Theme.Material.Light.NoActionBar" />
<style name="Theme.Material.Light.Dialog.BaseAlert">
- <item name="windowBackground">@color/transparent</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
<item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
<item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
@@ -1218,13 +1284,25 @@
<style name="Theme.Material.Light.Dialog.BaseTimePicker">
<item name="windowBackground">@color/transparent</item>
+ <item name="windowElevation">0dp</item>
<item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
+ <item name="windowContentOverlay">@null</item>
</style>
<!-- Material Light theme for the TimePicker dialog windows, which is used by the
{@link android.app.TimePickerDialog} class. -->
<style name="Theme.Material.Light.Dialog.TimePicker" parent="Theme.Material.Light.Dialog.BaseTimePicker"/>
+ <style name="Theme.Material.Light.Dialog.BaseDatePicker">
+ <item name="windowBackground">@color/transparent</item>
+ <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item>
+ <item name="windowContentOverlay">@null</item>
+ </style>
+
+ <!-- Material Light theme for the DatePicker dialog windows, which is used by the
+ {@link android.app.DatePickerDialog} class. -->
+ <style name="Theme.Material.Light.Dialog.DatePicker" parent="Theme.Material.Light.Dialog.BaseDatePicker"/>
+
<!-- Theme for a presentation window on a secondary display. -->
<style name="Theme.Material.Light.Dialog.Presentation" parent="@style/Theme.Material.Light.NoActionBar.Fullscreen" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index a2cc40c..b524177 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1257,4 +1257,11 @@
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.frameworks.coretests"
android:label="Frameworks Core Tests" />
+ <key-sets>
+ <key-set android:name="A" >
+ <public-key android:name="keyA"
+ android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="/>
+ </key-set>
+ <upgrade-key-set android:name="A"/>
+ </key-sets>
</manifest>
diff --git a/core/tests/coretests/apks/keyset/Android.mk b/core/tests/coretests/apks/keyset/Android.mk
index e44ac6c..306dc90 100644
--- a/core/tests/coretests/apks/keyset/Android.mk
+++ b/core/tests/coretests/apks/keyset/Android.mk
@@ -88,4 +88,21 @@
LOCAL_CERTIFICATE := $(LOCAL_PATH)/../../certs/keyset_A
LOCAL_ADDITIONAL_CERTIFICATES := $(LOCAL_PATH)/../../certs/keyset_B
LOCAL_MANIFEST_FILE := uB/AndroidManifest.xml
+include $(FrameworkCoreTests_BUILD_PACKAGE)
+
+#apks signed by platform only
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_PACKAGE_NAME := keyset_splat_api
+LOCAL_CERTIFICATE := platform
+LOCAL_MANIFEST_FILE := api_test/AndroidManifest.xml
+include $(FrameworkCoreTests_BUILD_PACKAGE)
+
+#apks signed by platform and keyset_A
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_PACKAGE_NAME := keyset_splata_api
+LOCAL_CERTIFICATE := platform
+LOCAL_ADDITIONAL_CERTIFICATES := $(LOCAL_PATH)/../../certs/keyset_A
+LOCAL_MANIFEST_FILE := api_test/AndroidManifest.xml
include $(FrameworkCoreTests_BUILD_PACKAGE)
\ No newline at end of file
diff --git a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml b/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
new file mode 100644
index 0000000..4c7e968
--- /dev/null
+++ b/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.keysets_api">
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 0244425..3a80309 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.KeySet;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -33,6 +34,7 @@
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
@@ -3328,6 +3330,174 @@
}
/**
+ * The following tests are related to testing KeySets-based API
+ */
+
+ /*
+ * testGetSigningKeySetNull - ensure getSigningKeySet() returns null on null
+ * input and when calling a package other than that which made the call.
+ */
+ public void testGetSigningKeySet() throws Exception {
+ PackageManager pm = getPm();
+ String mPkgName = mContext.getPackageName();
+ String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+ KeySet ks;
+ try {
+ ks = pm.getSigningKeySet(null);
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ ks = pm.getSigningKeySet("keysets.test.bogus.package");
+ assertTrue(false); // should have thrown
+ } catch (IllegalArgumentException e) {
+ }
+ installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ try {
+ ks = pm.getSigningKeySet(otherPkgName);
+ assertTrue(false); // should have thrown
+ } catch (SecurityException e) {
+ }
+ cleanUpInstall(otherPkgName);
+ ks = pm.getSigningKeySet(mContext.getPackageName());
+ assertNotNull(ks);
+ }
+
+ /*
+ * testGetKeySetByAlias - same as getSigningKeySet, but for keysets defined
+ * by this package.
+ */
+ public void testGetKeySetByAlias() throws Exception {
+ PackageManager pm = getPm();
+ String mPkgName = mContext.getPackageName();
+ String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+ KeySet ks;
+ try {
+ ks = pm.getKeySetByAlias(null, null);
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ ks = pm.getKeySetByAlias(null, "keysetBogus");
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ ks = pm.getKeySetByAlias("keysets.test.bogus.package", null);
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ ks = pm.getKeySetByAlias("keysets.test.bogus.package", "A");
+ assertTrue(false); // should have thrown
+ } catch(IllegalArgumentException e) {
+ }
+ try {
+ ks = pm.getKeySetByAlias(mPkgName, "keysetBogus");
+ assertTrue(false); // should have thrown
+ } catch(IllegalArgumentException e) {
+ }
+ installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ try {
+ ks = pm.getKeySetByAlias(otherPkgName, "A");
+ assertTrue(false); // should have thrown
+ } catch (SecurityException e) {
+ }
+ cleanUpInstall(otherPkgName);
+ ks = pm.getKeySetByAlias(mPkgName, "A");
+ assertNotNull(ks);
+ }
+
+ public void testIsSignedBy() throws Exception {
+ PackageManager pm = getPm();
+ String mPkgName = mContext.getPackageName();
+ String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+ KeySet mSigningKS = pm.getSigningKeySet(mPkgName);
+ KeySet mDefinedKS = pm.getKeySetByAlias(mPkgName, "A");
+
+ try {
+ assertFalse(pm.isSignedBy(null, null));
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ assertFalse(pm.isSignedBy(null, mSigningKS));
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ assertFalse(pm.isSignedBy(mPkgName, null));
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ assertFalse(pm.isSignedBy("keysets.test.bogus.package", mDefinedKS));
+ } catch(IllegalArgumentException e) {
+ }
+ assertFalse(pm.isSignedBy(mPkgName, mDefinedKS));
+ assertFalse(pm.isSignedBy(mPkgName, new KeySet(new Binder())));
+ assertTrue(pm.isSignedBy(mPkgName, mSigningKS));
+
+ installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ assertFalse(pm.isSignedBy(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+ cleanUpInstall(otherPkgName);
+
+ installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+ 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ assertTrue(pm.isSignedBy(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+ cleanUpInstall(otherPkgName);
+ }
+
+ public void testIsSignedByExactly() throws Exception {
+ PackageManager pm = getPm();
+ String mPkgName = mContext.getPackageName();
+ String otherPkgName = "com.android.frameworks.coretests.keysets_api";
+ KeySet mSigningKS = pm.getSigningKeySet(mPkgName);
+ KeySet mDefinedKS = pm.getKeySetByAlias(mPkgName, "A");
+ try {
+ assertFalse(pm.isSignedBy(null, null));
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ assertFalse(pm.isSignedBy(null, mSigningKS));
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ assertFalse(pm.isSignedBy(mPkgName, null));
+ assertTrue(false); // should have thrown
+ } catch (NullPointerException e) {
+ }
+ try {
+ assertFalse(pm.isSignedByExactly("keysets.test.bogus.package", mDefinedKS));
+ } catch(IllegalArgumentException e) {
+ }
+ assertFalse(pm.isSignedByExactly(mPkgName, mDefinedKS));
+ assertFalse(pm.isSignedByExactly(mPkgName, new KeySet(new Binder())));
+ assertTrue(pm.isSignedByExactly(mPkgName, mSigningKS));
+
+ installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedByExactly(otherPkgName, mSigningKS));
+ cleanUpInstall(otherPkgName);
+
+ installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+ 0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+ assertFalse(pm.isSignedByExactly(otherPkgName, mSigningKS));
+ cleanUpInstall(otherPkgName);
+ }
+
+
+
+ /**
* The following tests are related to testing the checkSignatures api.
*/
private void checkSignatures(int apk1, int apk2, int expMatchResult) throws Exception {
diff --git a/docs/html/tools/publishing/preparing.jd b/docs/html/tools/publishing/preparing.jd
index 7192aa8..5265fce 100644
--- a/docs/html/tools/publishing/preparing.jd
+++ b/docs/html/tools/publishing/preparing.jd
@@ -191,6 +191,13 @@
added to your code, such as {@link android.os.Debug#startMethodTracing()} and
{@link android.os.Debug#stopMethodTracing()} method calls.</p>
+<p class="caution"><strong>Important:</strong> Ensure that you disable debugging for
+your app if using {@link android.webkit.WebView} to display paid for content or if using JavaScript
+interfaces, since debugging allows users to inject scripts and extract content using Chrome
+DevTools. To disable debugging, use the
+{@link android.webkit.WebView#setWebContentsDebuggingEnabled(boolean) WebView.setWebContentsDebuggingEnabled()}
+method.</p>
+
<h4>Clean up your project directories</h4>
<p>Clean up your project and make sure it conforms to the directory structure described in <a
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 9561ac4..4b00e22 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -73,8 +73,8 @@
*/
private boolean mRequestPremultiplied;
- private byte[] mNinePatchChunk; // may be null
- private int[] mOpticalInsets; // may be null
+ private byte[] mNinePatchChunk; // may be null
+ private NinePatch.InsetStruct mNinePatchInsets; // may be null
private int mWidth;
private int mHeight;
private boolean mRecycled;
@@ -111,7 +111,7 @@
@SuppressWarnings({"UnusedDeclaration"}) // called from JNI
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
- byte[] ninePatchChunk, int[] opticalInsets) {
+ byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}
@@ -126,7 +126,7 @@
mFinalizer = new BitmapFinalizer(nativeBitmap);
mNinePatchChunk = ninePatchChunk;
- mOpticalInsets = opticalInsets;
+ mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
mDensity = density;
}
@@ -946,16 +946,18 @@
* @hide
*/
public void getOpticalInsets(@NonNull Rect outInsets) {
- if (mOpticalInsets == null) {
+ if (mNinePatchInsets == null) {
outInsets.setEmpty();
} else {
- outInsets.left = mOpticalInsets[0];
- outInsets.top = mOpticalInsets[1];
- outInsets.right = mOpticalInsets[2];
- outInsets.bottom = mOpticalInsets[3];
+ outInsets.set(mNinePatchInsets.opticalRect);
}
}
+ /** @hide */
+ public NinePatch.InsetStruct getNinePatchInsets() {
+ return mNinePatchInsets;
+ }
+
/**
* Specifies the known formats a bitmap can be compressed into
*/
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 99596ef..5211762 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -158,7 +158,7 @@
if (nativeCanvas == 0) {
throw new IllegalStateException();
}
- mNativeCanvasWrapper = initCanvas(nativeCanvas);
+ mNativeCanvasWrapper = nativeCanvas;
mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
mDensity = Bitmap.getDefaultDensity();
}
@@ -921,7 +921,7 @@
* @param b blue component (0..255) of the color to draw onto the canvas
*/
public void drawRGB(int r, int g, int b) {
- native_drawRGB(mNativeCanvasWrapper, r, g, b);
+ drawColor(Color.rgb(r, g, b));
}
/**
@@ -934,7 +934,7 @@
* @param b blue component (0..255) of the color to draw onto the canvas
*/
public void drawARGB(int a, int r, int g, int b) {
- native_drawARGB(mNativeCanvasWrapper, a, r, g, b);
+ drawColor(Color.argb(a, r, g, b));
}
/**
@@ -944,7 +944,7 @@
* @param color the color to draw onto the canvas
*/
public void drawColor(int color) {
- native_drawColor(mNativeCanvasWrapper, color);
+ native_drawColor(mNativeCanvasWrapper, color, PorterDuff.Mode.SRC_OVER.nativeInt);
}
/**
@@ -1298,13 +1298,28 @@
*/
public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,
@Nullable Paint paint) {
- if (dst == null) {
- throw new NullPointerException();
- }
- throwIfCannotDraw(bitmap);
- native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst,
- paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity);
- }
+ if (dst == null) {
+ throw new NullPointerException();
+ }
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+
+ float left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
+ }
/**
* Draw the specified bitmap, scaling/translating automatically to fill
@@ -1334,8 +1349,23 @@
throw new NullPointerException();
}
throwIfCannotDraw(bitmap);
- native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), src, dst,
- paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity);
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+
+ int left, top, right, bottom;
+ if (src == null) {
+ left = top = 0;
+ right = bitmap.getWidth();
+ bottom = bitmap.getHeight();
+ } else {
+ left = src.left;
+ right = src.right;
+ top = src.top;
+ bottom = src.bottom;
+ }
+
+ native_drawBitmap(mNativeCanvasWrapper, bitmap.ni(), left, top, right, bottom,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
+ bitmap.mDensity);
}
/**
@@ -1863,7 +1893,6 @@
public static native void freeTextLayoutCaches();
private static native long initRaster(long nativeBitmapOrZero);
- private static native long initCanvas(long canvasHandle);
private static native void native_setBitmap(long canvasHandle,
long bitmapHandle,
boolean copyState);
@@ -1916,11 +1945,6 @@
private static native boolean native_quickReject(long nativeCanvas,
float left, float top,
float right, float bottom);
- private static native void native_drawRGB(long nativeCanvas, int r, int g,
- int b);
- private static native void native_drawARGB(long nativeCanvas, int a, int r,
- int g, int b);
- private static native void native_drawColor(long nativeCanvas, int color);
private static native void native_drawColor(long nativeCanvas, int color,
int mode);
private static native void native_drawPaint(long nativeCanvas,
@@ -1962,16 +1986,9 @@
int screenDensity,
int bitmapDensity);
private native void native_drawBitmap(long nativeCanvas, long nativeBitmap,
- Rect src, RectF dst,
- long nativePaintOrZero,
- int screenDensity,
- int bitmapDensity);
- private static native void native_drawBitmap(long nativeCanvas,
- long nativeBitmap,
- Rect src, Rect dst,
- long nativePaintOrZero,
- int screenDensity,
- int bitmapDensity);
+ float srcLeft, float srcTop, float srcRight, float srcBottom,
+ float dstLeft, float dstTop, float dstRight, float dstBottom,
+ long nativePaintOrZero, int screenDensity, int bitmapDensity);
private static native void native_drawBitmap(long nativeCanvas, int[] colors,
int offset, int stride, float x,
float y, int width, int height,
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index befac92..335bce0 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -16,7 +16,6 @@
package android.graphics;
-
/**
* The NinePatch class permits drawing a bitmap in nine or more sections.
* Essentially, it allows the creation of custom graphics that will scale the
@@ -32,6 +31,39 @@
* </p>
*/
public class NinePatch {
+ /**
+ * Struct of inset information attached to a 9 patch bitmap.
+ *
+ * Present on a 9 patch bitmap if it optical insets were manually included,
+ * or if outline insets were automatically included by aapt.
+ *
+ * @hide
+ */
+ public static class InsetStruct {
+ @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
+ InsetStruct(int opticalLeft, int opticalTop, int opticalRight, int opticalBottom,
+ int outlineLeft, int outlineTop, int outlineRight, int outlineBottom,
+ float outlineRadius, boolean outlineFilled, float decodeScale) {
+ opticalRect = new Rect(opticalLeft, opticalTop, opticalRight, opticalBottom);
+ outlineRect = new Rect(outlineLeft, outlineTop, outlineRight, outlineBottom);
+
+ if (decodeScale != 1.0f) {
+ // if bitmap was scaled when decoded, scale the insets from the metadata values
+ opticalRect.scale(decodeScale);
+
+ // round inward while scaling outline, as the outline should always be conservative
+ outlineRect.scaleRoundIn(decodeScale);
+ }
+ this.outlineRadius = outlineRadius * decodeScale;
+ this.outlineFilled = outlineFilled;
+ }
+
+ public final Rect opticalRect;
+ public final Rect outlineRect;
+ public final float outlineRadius;
+ public final boolean outlineFilled;
+ }
+
private final Bitmap mBitmap;
/**
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 437d2f4..a9a8f37 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -609,4 +609,17 @@
bottom = (int) (bottom * scale + 0.5f);
}
}
+
+ /**
+ * Scales up the rect by the given scale, rounding values toward the inside.
+ * @hide
+ */
+ public void scaleRoundIn(float scale) {
+ if (scale != 1.0f) {
+ left = (int) Math.ceil(left * scale);
+ top = (int) Math.ceil(top * scale);
+ right = (int) Math.floor(right * scale);
+ bottom = (int) Math.floor(bottom * scale);
+ }
+ }
}
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 1cecef3..b5fc628 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -209,6 +209,7 @@
if (drawableRes != 0) {
mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable(
drawableRes, theme).mutate();
+ mAnimatedVectorState.mVectorDrawable.setAllowCaching(false);
}
a.recycle();
} else if (TARGET.equals(tagName)) {
@@ -258,6 +259,7 @@
mChangingConfigurations = copy.mChangingConfigurations;
// TODO: Make sure the constant state are handled correctly.
mVectorDrawable = new VectorDrawable();
+ mVectorDrawable.setAllowCaching(false);
mAnimators = new ArrayList<Animator>();
}
}
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 24bbf7c..c0110c9 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -16,6 +16,7 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -26,6 +27,7 @@
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.NinePatch;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
@@ -281,14 +283,35 @@
return false;
}
+ @Override
+ public boolean getOutline(@NonNull Outline outline) {
+ final Rect bounds = getBounds();
+ if (bounds.isEmpty()) return false;
+
+ if (mNinePatchState != null) {
+ NinePatch.InsetStruct insets = mNinePatchState.getBitmap().getNinePatchInsets();
+ if (insets != null) {
+ final Rect outlineInsets = insets.outlineRect;
+ outline.setRoundRect(bounds.left + outlineInsets.left,
+ bounds.top + outlineInsets.top,
+ bounds.right - outlineInsets.right,
+ bounds.bottom - outlineInsets.bottom,
+ insets.outlineRadius);
+ outline.setFilled(insets.outlineFilled);
+ return true;
+ }
+ }
+ return super.getOutline(outline);
+ }
+
/**
* @hide
*/
@Override
public Insets getOpticalInsets() {
if (needsMirroring()) {
- return Insets.of(mOpticalInsets.right, mOpticalInsets.top, mOpticalInsets.right,
- mOpticalInsets.bottom);
+ return Insets.of(mOpticalInsets.right, mOpticalInsets.top,
+ mOpticalInsets.left, mOpticalInsets.bottom);
} else {
return mOpticalInsets;
}
@@ -574,7 +597,7 @@
}
NinePatchState(NinePatch ninePatch, Rect padding) {
- this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false);
+ this(ninePatch, padding, null, DEFAULT_DITHER, false);
}
NinePatchState(NinePatch ninePatch, Rect padding, Rect opticalInsets) {
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 8783994..8c907b2 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -18,6 +18,7 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -142,6 +143,10 @@
private boolean mMutated;
+ // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise,
+ // caching the bitmap by default is allowed.
+ private boolean mAllowCaching = true;
+
public VectorDrawable() {
mVectorState = new VectorDrawableState();
}
@@ -183,7 +188,23 @@
final int saveCount = canvas.save();
final Rect bounds = getBounds();
canvas.translate(bounds.left, bounds.top);
- mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height());
+
+ if (!mAllowCaching) {
+ mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height());
+ } else {
+ Bitmap bitmap = mVectorState.mCachedBitmap;
+ if (bitmap == null || !mVectorState.canReuseCache(bounds.width(),
+ bounds.height())) {
+ bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(),
+ Bitmap.Config.ARGB_8888);
+ Canvas tmpCanvas = new Canvas(bitmap);
+ mVectorState.mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height());
+ mVectorState.mCachedBitmap = bitmap;
+
+ mVectorState.updateCacheStates();
+ }
+ canvas.drawBitmap(bitmap, null, bounds, null);
+ }
canvas.restoreToCount(saveCount);
}
@@ -444,6 +465,10 @@
return super.getChangingConfigurations() | mVectorState.mChangingConfigurations;
}
+ void setAllowCaching(boolean allowCaching) {
+ mAllowCaching = allowCaching;
+ }
+
private static class VectorDrawableState extends ConstantState {
int[] mThemeAttrs;
int mChangingConfigurations;
@@ -451,6 +476,12 @@
ColorStateList mTint;
Mode mTintMode;
+ Bitmap mCachedBitmap;
+ int[] mCachedThemeAttrs;
+ ColorStateList mCachedTint;
+ Mode mCachedTintMode;
+ int mCachedRootAlpha;
+
// Deep copy for mutate() or implicitly mutate.
public VectorDrawableState(VectorDrawableState copy) {
if (copy != null) {
@@ -462,6 +493,27 @@
}
}
+ public boolean canReuseCache(int width, int height) {
+ if (mCachedThemeAttrs == mThemeAttrs
+ && mCachedTint == mTint
+ && mCachedTintMode == mTintMode
+ && width == mCachedBitmap.getWidth()
+ && height == mCachedBitmap.getHeight()
+ && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) {
+ return true;
+ }
+ return false;
+ }
+
+ public void updateCacheStates() {
+ // Use shallow copy here and shallow comparison in canReuseCache(),
+ // likely hit cache miss more, but practically not much difference.
+ mCachedThemeAttrs = mThemeAttrs;
+ mCachedTint = mTint;
+ mCachedTintMode = mTintMode;
+ mCachedRootAlpha = mVPathRenderer.getRootAlpha();
+ }
+
public VectorDrawableState() {
mVPathRenderer = new VPathRenderer();
}
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index c9f541b..7fa1975 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2351,7 +2351,8 @@
return DrawGlInfo::kStatusDone;
}
- const Rect& bounds = vertexBuffer.getBounds();
+ Rect bounds(vertexBuffer.getBounds());
+ bounds.translate(translateX, translateY);
dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
int color = paint->getColor();
diff --git a/media/java/android/media/AudioDevice.java b/media/java/android/media/AudioDevice.java
index 1fd27fe..96d6196 100644
--- a/media/java/android/media/AudioDevice.java
+++ b/media/java/android/media/AudioDevice.java
@@ -66,8 +66,20 @@
return mConfig.port().address();
}
+ /** @hide */
+ public static int convertDeviceTypeToInternalDevice(int deviceType) {
+ return EXT_TO_INT_DEVICE_MAPPING.get(deviceType, AudioSystem.DEVICE_NONE);
+ }
+
+ /** @hide */
+ public static int convertInternalDeviceToDeviceType(int intDevice) {
+ return INT_TO_EXT_DEVICE_MAPPING.get(intDevice, DEVICE_TYPE_UNKNOWN);
+ }
+
private static final SparseIntArray INT_TO_EXT_DEVICE_MAPPING;
+ private static final SparseIntArray EXT_TO_INT_DEVICE_MAPPING;
+
static {
INT_TO_EXT_DEVICE_MAPPING = new SparseIntArray();
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_EARPIECE, DEVICE_TYPE_BUILTIN_EARPIECE);
@@ -110,6 +122,27 @@
// not covered here, legacy
//AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
//AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+
+ // privileges mapping to output device
+ EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_EARPIECE, AudioSystem.DEVICE_OUT_EARPIECE);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_SPEAKER, AudioSystem.DEVICE_OUT_SPEAKER);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_WIRED_HEADSET, AudioSystem.DEVICE_OUT_WIRED_HEADSET);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_WIRED_HEADPHONES, AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_LINE_ANALOG, AudioSystem.DEVICE_OUT_LINE);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_LINE_DIGITAL, AudioSystem.DEVICE_OUT_SPDIF);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BLUETOOTH_SCO, AudioSystem.DEVICE_OUT_BLUETOOTH_SCO);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BLUETOOTH_A2DP, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_HDMI, AudioSystem.DEVICE_OUT_HDMI);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_HDMI_ARC, AudioSystem.DEVICE_OUT_HDMI_ARC);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_USB_DEVICE, AudioSystem.DEVICE_OUT_USB_DEVICE);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_USB_ACCESSORY, AudioSystem.DEVICE_OUT_USB_ACCESSORY);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_DOCK, AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_FM, AudioSystem.DEVICE_OUT_FM);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_BUILTIN_MIC, AudioSystem.DEVICE_IN_BUILTIN_MIC);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_FM_TUNER, AudioSystem.DEVICE_IN_FM_TUNER);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER);
+ EXT_TO_INT_DEVICE_MAPPING.put(DEVICE_TYPE_TELEPHONY, AudioSystem.DEVICE_OUT_TELEPHONY_TX);
}
}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 025d354..79be108 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -78,9 +78,9 @@
public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100;
public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200;
public static final int CHANNEL_OUT_BACK_CENTER = 0x400;
- /** @hide */
+ /** @hide CANDIDATE FOR PUBLIC API */
public static final int CHANNEL_OUT_SIDE_LEFT = 0x800;
- /** @hide */
+ /** @hide CANDIDATE FOR PUBLIC API */
public static final int CHANNEL_OUT_SIDE_RIGHT = 0x1000;
/** @hide */
public static final int CHANNEL_OUT_TOP_CENTER = 0x2000;
@@ -128,6 +128,35 @@
CHANNEL_OUT_LOW_FREQUENCY);
// CHANNEL_OUT_ALL is not yet defined; if added then it should match AUDIO_CHANNEL_OUT_ALL
+ /**
+ * @hide
+ * Return the number of channels from an output channel mask
+ * @param mask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
+ * @return number of channels for the mask
+ */
+ public static int channelCountFromOutChannelMask(int mask) {
+ return Integer.bitCount(mask);
+ }
+ /**
+ * @hide
+ * Return a channel mask ready to be used by native code
+ * @param mask a combination of the CHANNEL_OUT_* definitions, but not CHANNEL_OUT_DEFAULT
+ * @return a native channel mask
+ */
+ public static int convertChannelOutMaskToNativeMask(int javaMask) {
+ return (javaMask >> 2);
+ }
+
+ /**
+ * @hide
+ * Return a java output channel mask
+ * @param mask a native channel mask
+ * @return a combination of the CHANNEL_OUT_* definitions
+ */
+ public static int convertNativeChannelMaskToOutMask(int nativeMask) {
+ return (nativeMask << 2);
+ }
+
public static final int CHANNEL_IN_DEFAULT = 1;
// These directly match native
public static final int CHANNEL_IN_LEFT = 0x4;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index cb9776a..4abcb81 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1157,6 +1157,9 @@
* @deprecated Use the new {@link #getInputBuffer} method instead
* each time an input buffer is dequeued.
*
+ * <b>Note:</b>As of API 21, dequeued input buffers are
+ * automatically {@link java.nio.Buffer#clear cleared}.
+ *
* @throws IllegalStateException if not in the Executing state.
* @throws MediaCodec.CodecException upon codec error.
*/
@@ -1180,6 +1183,10 @@
* each time an output buffer is dequeued. This method is not
* supported if codec is configured in asynchronous mode.
*
+ * <b>Note:</b>As of API 21, the position and limit of output
+ * buffers that are dequeued will be set to the valid data
+ * range.
+ *
* @throws IllegalStateException if not in the Executing state,
* or codec is configured in asynchronous mode.
* @throws MediaCodec.CodecException upon codec error.
@@ -1213,8 +1220,8 @@
}
/**
- * Returns a cleared, writable ByteBuffer object for a dequeued
- * input buffer index to contain the input data.
+ * Returns a {@link java.nio.Buffer#clear cleared}, writable ByteBuffer
+ * object for a dequeued input buffer index to contain the input data.
*
* After calling this method any ByteBuffer or Image object
* previously returned for the same input index MUST no longer
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index f2753ee..b23b540 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -297,8 +297,12 @@
/**
* Retrieve the current encoded sample and store it in the byte buffer
- * starting at the given offset. Returns the sample size (or -1 if
- * no more samples are available).
+ * starting at the given offset.
+ * <p>
+ * <b>Note:</b>As of API 21, on success the position and limit of
+ * {@code byteBuf} is updated to point to the data just read.
+ * @param byteBuf the destination byte buffer
+ * @return the sample size (or -1 if no more samples are available).
*/
public native int readSampleData(ByteBuffer byteBuf, int offset);
@@ -316,7 +320,10 @@
// Keep these in sync with their equivalents in NuMediaExtractor.h
/**
- * The sample is a sync sample
+ * The sample is a sync sample (or in {@link MediaCodec}'s terminology
+ * it is a key frame.)
+ *
+ * @see MediaCodec#BUFFER_FLAG_KEY_FRAME
*/
public static final int SAMPLE_FLAG_SYNC = 1;
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 73bc61a..740a9d3 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -682,9 +682,13 @@
// USE_SESSIONS
if (mSession != null) {
int pbState = PlaybackState.getStateFromRccState(state);
- mSessionPlaybackState.setState(pbState, hasPosition ?
- mPlaybackPositionMs : PlaybackState.PLAYBACK_POSITION_UNKNOWN,
- playbackSpeed);
+ long position = hasPosition ? mPlaybackPositionMs
+ : PlaybackState.PLAYBACK_POSITION_UNKNOWN;
+
+ PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
+ bob.setState(pbState, position, playbackSpeed, SystemClock.elapsedRealtime());
+ bob.setErrorMessage(null);
+ mSessionPlaybackState = bob.build();
mSession.setPlaybackState(mSessionPlaybackState);
}
}
@@ -745,8 +749,9 @@
// USE_SESSIONS
if (mSession != null) {
- mSessionPlaybackState.setActions(PlaybackState
- .getActionsFromRccControlFlags(transportControlFlags));
+ PlaybackState.Builder bob = new PlaybackState.Builder(mSessionPlaybackState);
+ bob.setActions(PlaybackState.getActionsFromRccControlFlags(transportControlFlags));
+ mSessionPlaybackState = bob.build();
mSession.setPlaybackState(mSessionPlaybackState);
}
}
@@ -946,7 +951,7 @@
/**
* Cache for the current playback state using Session APIs.
*/
- private final PlaybackState mSessionPlaybackState = new PlaybackState();
+ private PlaybackState mSessionPlaybackState = null;
/**
* Cache for metadata using Session APIs. This is re-initialized in apply().
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 1f5b216..9ea3f26 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -1020,7 +1020,7 @@
l.onClientPlaybackStateUpdate(playstate);
} else {
l.onClientPlaybackStateUpdate(playstate, state.getLastPositionUpdateTime(),
- state.getPosition(), state.getPlaybackRate());
+ state.getPosition(), state.getPlaybackSpeed());
}
if (state != null) {
l.onClientTransportControlUpdate(PlaybackState.getRccControlFlagsFromActions(state
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 9b381cc..9fa3f50 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -1282,7 +1282,7 @@
/**
* @hide
*/
- public int byteArrayToInt(byte[] valueBuf) {
+ public static int byteArrayToInt(byte[] valueBuf) {
return byteArrayToInt(valueBuf, 0);
}
@@ -1290,7 +1290,7 @@
/**
* @hide
*/
- public int byteArrayToInt(byte[] valueBuf, int offset) {
+ public static int byteArrayToInt(byte[] valueBuf, int offset) {
ByteBuffer converter = ByteBuffer.wrap(valueBuf);
converter.order(ByteOrder.nativeOrder());
return converter.getInt(offset);
@@ -1300,7 +1300,7 @@
/**
* @hide
*/
- public byte[] intToByteArray(int value) {
+ public static byte[] intToByteArray(int value) {
ByteBuffer converter = ByteBuffer.allocate(4);
converter.order(ByteOrder.nativeOrder());
converter.putInt(value);
@@ -1310,14 +1310,14 @@
/**
* @hide
*/
- public short byteArrayToShort(byte[] valueBuf) {
+ public static short byteArrayToShort(byte[] valueBuf) {
return byteArrayToShort(valueBuf, 0);
}
/**
* @hide
*/
- public short byteArrayToShort(byte[] valueBuf, int offset) {
+ public static short byteArrayToShort(byte[] valueBuf, int offset) {
ByteBuffer converter = ByteBuffer.wrap(valueBuf);
converter.order(ByteOrder.nativeOrder());
return converter.getShort(offset);
@@ -1327,7 +1327,7 @@
/**
* @hide
*/
- public byte[] shortToByteArray(short value) {
+ public static byte[] shortToByteArray(short value) {
ByteBuffer converter = ByteBuffer.allocate(2);
converter.order(ByteOrder.nativeOrder());
short sValue = (short) value;
@@ -1338,7 +1338,7 @@
/**
* @hide
*/
- public byte[] concatArrays(byte[]... arrays) {
+ public static byte[] concatArrays(byte[]... arrays) {
int len = 0;
for (byte[] a : arrays) {
len += a.length;
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 6b20006..136761b 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -16,9 +16,13 @@
package android.media.audiofx;
+import android.media.AudioDevice;
+import android.media.AudioFormat;
import android.media.audiofx.AudioEffect;
import android.util.Log;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.StringTokenizer;
@@ -44,8 +48,10 @@
public class Virtualizer extends AudioEffect {
private final static String TAG = "Virtualizer";
+ private final static boolean DEBUG = false;
- // These constants must be synchronized with those in frameworks/base/include/media/EffectVirtualizerApi.h
+ // These constants must be synchronized with those in
+ // system/media/audio_effects/include/audio_effects/effect_virtualizer.h
/**
* Is strength parameter supported by virtualizer engine. Parameter ID for getParameter().
*/
@@ -55,6 +61,21 @@
* {@link android.media.audiofx.Virtualizer.OnParameterChangeListener}
*/
public static final int PARAM_STRENGTH = 1;
+ /**
+ * @hide
+ * Parameter ID to query the virtual speaker angles for a channel mask / device configuration.
+ */
+ public static final int PARAM_VIRTUAL_SPEAKER_ANGLES = 2;
+ /**
+ * @hide
+ * Parameter ID to force the virtualization mode to be that of a specific device
+ */
+ public static final int PARAM_FORCE_VIRTUALIZATION_MODE = 3;
+ /**
+ * @hide
+ * Parameter ID to query the current virtualization mode.
+ */
+ public static final int PARAM_VIRTUALIZATION_MODE = 4;
/**
* Indicates if strength parameter is supported by the virtualizer engine
@@ -145,6 +166,223 @@
}
/**
+ * Checks if a configuration is supported, and query the virtual speaker angles.
+ * @param inputChannelMask
+ * @param deviceType
+ * @param angles if non-null: array in which the angles will be written. If null, no angles
+ * are returned
+ * @return true if the combination of channel mask and output device type is supported, false
+ * otherwise
+ * @throws IllegalStateException
+ * @throws IllegalArgumentException
+ * @throws UnsupportedOperationException
+ */
+ private boolean getAnglesInt(int inputChannelMask, int deviceType, int[] angles)
+ throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+ // parameter check
+ if (inputChannelMask == AudioFormat.CHANNEL_INVALID) {
+ throw (new IllegalArgumentException(
+ "Virtualizer: illegal CHANNEL_INVALID channel mask"));
+ }
+ int channelMask = inputChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT ?
+ AudioFormat.CHANNEL_OUT_STEREO : inputChannelMask;
+ int nbChannels = AudioFormat.channelCountFromOutChannelMask(channelMask);
+ if ((angles != null) && (angles.length < (nbChannels * 3))) {
+ Log.e(TAG, "Size of array for angles cannot accomodate number of channels in mask ("
+ + nbChannels + ")");
+ throw (new IllegalArgumentException(
+ "Virtualizer: array for channel / angle pairs is too small: is " + angles.length
+ + ", should be " + (nbChannels * 3)));
+ }
+
+ ByteBuffer paramsConverter = ByteBuffer.allocate(3 /* param + mask + device*/ * 4);
+ paramsConverter.order(ByteOrder.nativeOrder());
+ paramsConverter.putInt(PARAM_VIRTUAL_SPEAKER_ANGLES);
+ // convert channel mask to internal native representation
+ paramsConverter.putInt(AudioFormat.convertChannelOutMaskToNativeMask(channelMask));
+ // convert Java device type to internal representation
+ paramsConverter.putInt(AudioDevice.convertDeviceTypeToInternalDevice(deviceType));
+ // allocate an array to store the results
+ byte[] result = new byte[nbChannels * 4/*int to byte*/ * 3/*for mask, azimuth, elevation*/];
+
+ // call into the effect framework
+ int status = getParameter(paramsConverter.array(), result);
+ if (DEBUG) {
+ Log.v(TAG, "getAngles(0x" + Integer.toHexString(inputChannelMask) + ", 0x"
+ + Integer.toHexString(deviceType) + ") returns " + status);
+ }
+
+ if (status >= 0) {
+ if (angles != null) {
+ // convert and copy the results
+ ByteBuffer resultConverter = ByteBuffer.wrap(result);
+ resultConverter.order(ByteOrder.nativeOrder());
+ for (int i = 0 ; i < nbChannels ; i++) {
+ // write the channel mask
+ angles[3 * i] = AudioFormat.convertNativeChannelMaskToOutMask(
+ resultConverter.getInt((i * 4 * 3)));
+ // write the azimuth
+ angles[3 * i + 1] = resultConverter.getInt(i * 4 * 3 + 4);
+ // write the elevation
+ angles[3 * i + 2] = resultConverter.getInt(i * 4 * 3 + 8);
+ if (DEBUG) {
+ Log.v(TAG, "channel 0x" + Integer.toHexString(angles[3*i]).toUpperCase()
+ + " at az=" + angles[3*i+1] + "deg"
+ + " elev=" + angles[3*i+2] + "deg");
+ }
+ }
+ }
+ return true;
+ } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+ // a BAD_VALUE return from getParameter indicates the configuration is not supported
+ // don't throw an exception, just return false
+ return false;
+ } else {
+ // something wrong may have happened
+ checkStatus(status);
+ }
+ // unexpected virtualizer behavior
+ Log.e(TAG, "unexpected status code " + status
+ + " after getParameter(PARAM_VIRTUAL_SPEAKER_ANGLES)");
+ return false;
+ }
+
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * Checks if the combination of a channel mask and device type is supported by this virtualizer.
+ * Some virtualizer implementations may only support binaural processing (i.e. only support
+ * headphone output), some may support transaural processing (i.e. for speaker output) for the
+ * built-in speakers. Use this method to query the virtualizer implementation capabilities.
+ * @param inputChannelMask the channel mask of the content to virtualize.
+ * @param deviceType the device type for which virtualization processing is to be performed.
+ * Valid values are the device types defined in {@link AudioDevice}.
+ * @return true if the combination of channel mask and output device type is supported, false
+ * otherwise.
+ * <br>An indication that a certain channel mask is not supported doesn't necessarily mean
+ * you cannot play content with that channel mask, it more likely implies the content will
+ * be downmixed before being virtualized. For instance a virtualizer that only supports a
+ * mask such as {@link AudioFormat#CHANNEL_OUT_STEREO}
+ * will still be able to process content with a mask of
+ * {@link AudioFormat#CHANNEL_OUT_5POINT1}, but will downmix the content to stereo first, and
+ * then will virtualize, as opposed to virtualizing each channel individually.
+ * @throws IllegalStateException
+ * @throws IllegalArgumentException
+ * @throws UnsupportedOperationException
+ */
+ public boolean canVirtualize(int inputChannelMask, int deviceType)
+ throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+ return getAnglesInt(inputChannelMask, deviceType, null);
+ }
+
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * Queries the virtual speaker angles (azimuth and elevation) for a combination of a channel
+ * mask and device type.
+ * If the virtualization configuration (mask and device) is supported (see
+ * {@link #canVirtualize(int, int)}, the array angles will contain upon return the
+ * definition of each virtual speaker and its azimuth and elevation angles relative to the
+ * listener.
+ * <br>Note that in some virtualizer implementations, the angles may be strength-dependent.
+ * @param inputChannelMask the channel mask of the content to virtualize.
+ * @param deviceType the device type for which virtualization processing is to be performed.
+ * Valid values are the device types defined in {@link AudioDevice}.
+ * @param angles a non-null array whose length is 3 times the number of channels in the channel
+ * mask.
+ * If the method indicates the configuration is supported, the array will contain upon return
+ * triplets of values: for each channel <code>i</code> among the channels of the mask:
+ * <ul>
+ * <li>the element at index <code>3*i</code> in the array contains the speaker
+ * identification (e.g. {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT}),</li>
+ * <li>the element at index <code>3*i+1</code> contains its corresponding azimuth angle
+ * expressed in degrees, where 0 is the direction the listener faces, 180 is behind
+ * the listener, and -90 is to her/his left,</li>
+ * <li>the element at index <code>3*i+2</code> contains its corresponding elevation angle
+ * where +90 is directly above the listener, 0 is the horizontal plane, and -90 is
+ * directly below the listener.</li>
+ * @return true if the combination of channel mask and output device type is supported, false
+ * otherwise.
+ * @throws IllegalStateException
+ * @throws IllegalArgumentException
+ * @throws UnsupportedOperationException
+ */
+ public boolean getSpeakerAngles(int inputChannelMask, int deviceType, int[] angles)
+ throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+ if (angles == null) {
+ throw (new IllegalArgumentException(
+ "Virtualizer: illegal null channel / angle array"));
+ }
+
+ return getAnglesInt(inputChannelMask, deviceType, angles);
+ }
+
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * Forces the virtualizer effect to use the processing mode used for the given device type.
+ * The effect must be enabled for the forced mode to be applied.
+ * @param deviceType one of the device types defined in {@link AudioDevice}.
+ * Use {@link AudioDevice#DEVICE_TYPE_UNKNOWN} to return to the non-forced mode.
+ * @return true if the processing mode for the device type is supported, and it is successfully
+ * set, or forcing was successfully disabled with {@link AudioDevice#DEVICE_TYPE_UNKNOWN},
+ * false otherwise.
+ * @throws IllegalStateException
+ * @throws IllegalArgumentException
+ * @throws UnsupportedOperationException
+ */
+ public boolean forceVirtualizationMode(int deviceType)
+ throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
+ // convert Java device type to internal representation
+ int internalDevice = AudioDevice.convertDeviceTypeToInternalDevice(deviceType);
+
+ int status = setParameter(PARAM_FORCE_VIRTUALIZATION_MODE, internalDevice);
+
+ if (status >= 0) {
+ return true;
+ } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+ // a BAD_VALUE return from setParameter indicates the mode can't be forced to that
+ // of this device, don't throw an exception, just return false
+ return false;
+ } else {
+ // something wrong may have happened
+ checkStatus(status);
+ }
+ // unexpected virtualizer behavior
+ Log.e(TAG, "unexpected status code " + status
+ + " after setParameter(PARAM_FORCE_VIRTUALIZATION_MODE)");
+ return false;
+ }
+
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * Return the device type which reflects the virtualization mode being used, if any.
+ * @return a device type (as defined in {@link AudioDevice}) which reflects the virtualization
+ * mode being used.
+ * If virtualization is not active, the device type will be
+ * {@link AudioDevice#DEVICE_TYPE_UNKNOWN}. Virtualization may not be active either because
+ * the effect is not enabled or because the current output device is not compatible with
+ * this virtualization implementation.
+ */
+ public int getVirtualizationMode() {
+ int[] value = new int[1];
+ int status = getParameter(PARAM_VIRTUALIZATION_MODE, value);
+ if (status >= 0) {
+ return AudioDevice.convertInternalDeviceToDeviceType(value[0]);
+ } else if (status == AudioEffect.ERROR_BAD_VALUE) {
+ return AudioDevice.DEVICE_TYPE_UNKNOWN;
+ } else {
+ // something wrong may have happened
+ checkStatus(status);
+ }
+ // unexpected virtualizer behavior
+ Log.e(TAG, "unexpected status code " + status
+ + " after getParameter(PARAM_VIRTUALIZATION_MODE)");
+ return AudioDevice.DEVICE_TYPE_UNKNOWN;
+ }
+
+ /**
* The OnParameterChangeListener interface defines a method called by the Virtualizer when a
* parameter value has changed.
*/
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 9ae2436..f7e7176 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -184,41 +184,29 @@
*/
public final static long PLAYBACK_POSITION_UNKNOWN = -1;
- private int mState;
- private long mPosition;
- private long mBufferPosition;
- private float mRate;
- private long mActions;
- private CharSequence mErrorMessage;
- private long mUpdateTime;
+ private final int mState;
+ private final long mPosition;
+ private final long mBufferPosition;
+ private final float mSpeed;
+ private final long mActions;
+ private final CharSequence mErrorMessage;
+ private final long mUpdateTime;
- /**
- * Create an empty PlaybackState. At minimum a state and actions should be
- * set before publishing a PlaybackState.
- */
- public PlaybackState() {
- }
-
- /**
- * Create a new PlaybackState from an existing PlaybackState. All fields
- * will be copied to the new state.
- *
- * @param from The PlaybackState to duplicate
- */
- public PlaybackState(PlaybackState from) {
- mState = from.mState;
- mPosition = from.mPosition;
- mRate = from.mRate;
- mUpdateTime = from.mUpdateTime;
- mBufferPosition = from.mBufferPosition;
- mActions = from.mActions;
- mErrorMessage = from.mErrorMessage;
+ private PlaybackState(int state, long position, long updateTime, float speed,
+ long bufferPosition, long actions, CharSequence error) {
+ mState = state;
+ mPosition = position;
+ mSpeed = speed;
+ mUpdateTime = updateTime;
+ mBufferPosition = bufferPosition;
+ mActions = actions;
+ mErrorMessage = error;
}
private PlaybackState(Parcel in) {
mState = in.readInt();
mPosition = in.readLong();
- mRate = in.readFloat();
+ mSpeed = in.readFloat();
mUpdateTime = in.readLong();
mBufferPosition = in.readLong();
mActions = in.readLong();
@@ -232,7 +220,7 @@
bob.append("state=").append(mState);
bob.append(", position=").append(mPosition);
bob.append(", buffered position=").append(mBufferPosition);
- bob.append(", rate=").append(mRate);
+ bob.append(", speed=").append(mSpeed);
bob.append(", updated=").append(mUpdateTime);
bob.append(", actions=").append(mActions);
bob.append(", error=").append(mErrorMessage);
@@ -249,7 +237,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mState);
dest.writeLong(mPosition);
- dest.writeFloat(mRate);
+ dest.writeFloat(mSpeed);
dest.writeLong(mUpdateTime);
dest.writeLong(mBufferPosition);
dest.writeLong(mActions);
@@ -271,41 +259,6 @@
public int getState() {
return mState;
}
-
- /**
- * Set the current state of playback.
- * <p>
- * The position must be in ms and indicates the current playback position
- * within the track. If the position is unknown use
- * {@link #PLAYBACK_POSITION_UNKNOWN}.
- * <p>
- * The rate is a multiple of normal playback and should be 0 when paused and
- * negative when rewinding. Normal playback rate is 1.0.
- * <p>
- * The state must be one of the following:
- * <ul>
- * <li> {@link PlaybackState#STATE_NONE}</li>
- * <li> {@link PlaybackState#STATE_STOPPED}</li>
- * <li> {@link PlaybackState#STATE_PLAYING}</li>
- * <li> {@link PlaybackState#STATE_PAUSED}</li>
- * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
- * <li> {@link PlaybackState#STATE_REWINDING}</li>
- * <li> {@link PlaybackState#STATE_BUFFERING}</li>
- * <li> {@link PlaybackState#STATE_ERROR}</li>
- * </ul>
- *
- * @param state The current state of playback.
- * @param position The position in the current track in ms.
- * @param playbackRate The current rate of playback as a multiple of normal
- * playback.
- */
- public void setState(int state, long position, float playbackRate) {
- this.mState = state;
- this.mPosition = position;
- this.mRate = playbackRate;
- mUpdateTime = SystemClock.elapsedRealtime();
- }
-
/**
* Get the current playback position in ms.
*/
@@ -323,23 +276,14 @@
}
/**
- * Set the current buffer position in ms. This is the farthest playback
- * point that can be reached from the current position using only buffered
- * content.
- */
- public void setBufferPosition(long bufferPosition) {
- mBufferPosition = bufferPosition;
- }
-
- /**
- * Get the current playback rate as a multiple of normal playback. This
+ * Get the current playback speed as a multiple of normal playback. This
* should be negative when rewinding. A value of 1 means normal playback and
* 0 means paused.
*
- * @return The current rate of playback.
+ * @return The current speed of playback.
*/
- public float getPlaybackRate() {
- return mRate;
+ public float getPlaybackSpeed() {
+ return mSpeed;
}
/**
@@ -362,25 +306,6 @@
}
/**
- * Set the current capabilities available on this session. This should use a
- * bitmask of the available capabilities.
- * <ul>
- * <li> {@link PlaybackState#ACTION_SKIP_TO_PREVIOUS}</li>
- * <li> {@link PlaybackState#ACTION_REWIND}</li>
- * <li> {@link PlaybackState#ACTION_PLAY}</li>
- * <li> {@link PlaybackState#ACTION_PAUSE}</li>
- * <li> {@link PlaybackState#ACTION_STOP}</li>
- * <li> {@link PlaybackState#ACTION_FAST_FORWARD}</li>
- * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li>
- * <li> {@link PlaybackState#ACTION_SEEK_TO}</li>
- * <li> {@link PlaybackState#ACTION_SET_RATING}</li>
- * </ul>
- */
- public void setActions(long capabilities) {
- mActions = capabilities;
- }
-
- /**
* Get a user readable error message. This should be set when the state is
* {@link PlaybackState#STATE_ERROR}.
*/
@@ -393,21 +318,12 @@
* position has never been set this will return 0;
*
* @return The last time the position was updated.
- * @hide
*/
public long getLastPositionUpdateTime() {
return mUpdateTime;
}
/**
- * Set a user readable error message. This should be set when the state is
- * {@link PlaybackState#STATE_ERROR}.
- */
- public void setErrorMessage(CharSequence errorMessage) {
- mErrorMessage = errorMessage;
- }
-
- /**
* Get the {@link PlaybackState} state for the given
* {@link RemoteControlClient} state.
*
@@ -574,4 +490,175 @@
return new PlaybackState[size];
}
};
+
+ /**
+ * Builder for {@link PlaybackState} objects.
+ */
+ public static final class Builder {
+ private int mState;
+ private long mPosition;
+ private long mBufferPosition;
+ private float mSpeed;
+ private long mActions;
+ private CharSequence mErrorMessage;
+ private long mUpdateTime;
+
+ /**
+ * Creates an initially empty state builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Creates a builder with the same initial values as those in the from
+ * state.
+ *
+ * @param from The state to use for initializing the builder.
+ */
+ public Builder(PlaybackState from) {
+ if (from == null) {
+ return;
+ }
+ mState = from.mState;
+ mPosition = from.mPosition;
+ mBufferPosition = from.mBufferPosition;
+ mSpeed = from.mSpeed;
+ mActions = from.mActions;
+ mErrorMessage = from.mErrorMessage;
+ mUpdateTime = from.mUpdateTime;
+ }
+
+ /**
+ * Set the current state of playback.
+ * <p>
+ * The position must be in ms and indicates the current playback
+ * position within the track. If the position is unknown use
+ * {@link #PLAYBACK_POSITION_UNKNOWN}. When not using an unknown
+ * position the time at which the position was updated must be provided.
+ * It is okay to use {@link SystemClock#elapsedRealtime()} if the
+ * current position was just retrieved.
+ * <p>
+ * The speed is a multiple of normal playback and should be 0 when
+ * paused and negative when rewinding. Normal playback speed is 1.0.
+ * <p>
+ * The state must be one of the following:
+ * <ul>
+ * <li> {@link PlaybackState#STATE_NONE}</li>
+ * <li> {@link PlaybackState#STATE_STOPPED}</li>
+ * <li> {@link PlaybackState#STATE_PLAYING}</li>
+ * <li> {@link PlaybackState#STATE_PAUSED}</li>
+ * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
+ * <li> {@link PlaybackState#STATE_REWINDING}</li>
+ * <li> {@link PlaybackState#STATE_BUFFERING}</li>
+ * <li> {@link PlaybackState#STATE_ERROR}</li>
+ * </ul>
+ *
+ * @param state The current state of playback.
+ * @param position The position in the current track in ms.
+ * @param playbackSpeed The current speed of playback as a multiple of
+ * normal playback.
+ * @param updateTime The time in the {@link SystemClock#elapsedRealtime}
+ * timebase that the position was updated at.
+ * @return this
+ */
+ public Builder setState(int state, long position, float playbackSpeed, long updateTime) {
+ mState = state;
+ mPosition = position;
+ mUpdateTime = updateTime;
+ mSpeed = playbackSpeed;
+ return this;
+ }
+
+ /**
+ * Set the current state of playback.
+ * <p>
+ * The position must be in ms and indicates the current playback
+ * position within the track. If the position is unknown use
+ * {@link #PLAYBACK_POSITION_UNKNOWN}. The update time will be set to
+ * the current {@link SystemClock#elapsedRealtime()}.
+ * <p>
+ * The speed is a multiple of normal playback and should be 0 when
+ * paused and negative when rewinding. Normal playback speed is 1.0.
+ * <p>
+ * The state must be one of the following:
+ * <ul>
+ * <li> {@link PlaybackState#STATE_NONE}</li>
+ * <li> {@link PlaybackState#STATE_STOPPED}</li>
+ * <li> {@link PlaybackState#STATE_PLAYING}</li>
+ * <li> {@link PlaybackState#STATE_PAUSED}</li>
+ * <li> {@link PlaybackState#STATE_FAST_FORWARDING}</li>
+ * <li> {@link PlaybackState#STATE_REWINDING}</li>
+ * <li> {@link PlaybackState#STATE_BUFFERING}</li>
+ * <li> {@link PlaybackState#STATE_ERROR}</li>
+ * </ul>
+ *
+ * @param state The current state of playback.
+ * @param position The position in the current track in ms.
+ * @param playbackSpeed The current speed of playback as a multiple of
+ * normal playback.
+ * @return this
+ */
+ public Builder setState(int state, long position, float playbackSpeed) {
+ return setState(state, position, playbackSpeed, SystemClock.elapsedRealtime());
+ }
+
+ /**
+ * Set the current actions available on this session. This should use a
+ * bitmask of possible actions.
+ * <ul>
+ * <li> {@link PlaybackState#ACTION_SKIP_TO_PREVIOUS}</li>
+ * <li> {@link PlaybackState#ACTION_REWIND}</li>
+ * <li> {@link PlaybackState#ACTION_PLAY}</li>
+ * <li> {@link PlaybackState#ACTION_PAUSE}</li>
+ * <li> {@link PlaybackState#ACTION_STOP}</li>
+ * <li> {@link PlaybackState#ACTION_FAST_FORWARD}</li>
+ * <li> {@link PlaybackState#ACTION_SKIP_TO_NEXT}</li>
+ * <li> {@link PlaybackState#ACTION_SEEK_TO}</li>
+ * <li> {@link PlaybackState#ACTION_SET_RATING}</li>
+ * </ul>
+ *
+ * @param actions The set of actions allowed.
+ * @return this
+ */
+ public Builder setActions(long actions) {
+ mActions = actions;
+ return this;
+ }
+
+ /**
+ * Set the current buffer position in ms. This is the farthest playback
+ * point that can be reached from the current position using only
+ * buffered content.
+ *
+ * @param bufferPosition The position in ms that playback is buffered
+ * to.
+ * @return this
+ */
+ public Builder setBufferPosition(long bufferPosition) {
+ mBufferPosition = bufferPosition;
+ return this;
+ }
+
+ /**
+ * Set a user readable error message. This should be set when the state
+ * is {@link PlaybackState#STATE_ERROR}.
+ *
+ * @param error The error message for display to the user.
+ * @return this
+ */
+ public Builder setErrorMessage(CharSequence error) {
+ mErrorMessage = error;
+ return this;
+ }
+
+ /**
+ * Build and return the PlaybackState instance with these values.
+ *
+ * @return A new state instance.
+ */
+ public PlaybackState build() {
+ return new PlaybackState(mState, mPosition, mUpdateTime, mSpeed, mBufferPosition,
+ mActions, mErrorMessage);
+ }
+ }
}
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index cac8a14..423e317 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -30,7 +30,6 @@
*/
oneway interface ITvInputClient {
void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
- void onAvailabilityChanged(in String inputId, boolean isAvailable);
void onSessionReleased(int seq);
void onSessionEvent(in String name, in Bundle args, int seq);
void onChannelRetuned(in Uri channelUri, int seq);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 9a6a648..6a0c592 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -18,9 +18,10 @@
import android.content.ComponentName;
import android.graphics.Rect;
+import android.media.tv.ITvInputClient;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
-import android.media.tv.ITvInputClient;
+import android.media.tv.ITvInputManagerCallback;
import android.media.tv.TvInputHardwareInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvTrackInfo;
@@ -34,10 +35,8 @@
interface ITvInputManager {
List<TvInputInfo> getTvInputList(int userId);
- boolean getAvailability(in ITvInputClient client, in String inputId, int userId);
-
- void registerCallback(in ITvInputClient client, in String inputId, int userId);
- void unregisterCallback(in ITvInputClient client, in String inputId, int userId);
+ void registerCallback(in ITvInputManagerCallback callback, int userId);
+ void unregisterCallback(in ITvInputManagerCallback callback, int userId);
void createSession(in ITvInputClient client, in String inputId, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
@@ -56,7 +55,12 @@
// For TV input hardware binding
List<TvInputHardwareInfo> getHardwareList();
+ /*
+ * All TvInputServices which want to use hardware must call this method on
+ * BOOT_COMPLETE.
+ */
+ void registerTvInputInfo(in TvInputInfo info, int deviceId);
ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
- int userId);
+ in TvInputInfo info, int userId);
void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
}
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl b/media/java/android/media/tv/ITvInputManagerCallback.aidl
similarity index 65%
copy from telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl
copy to media/java/android/media/tv/ITvInputManagerCallback.aidl
index 3a02b06..5c8a0a3 100644
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallSendDtmfCallback.aidl
+++ b/media/java/android/media/tv/ITvInputManagerCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.internal.telephony;
+package android.media.tv;
/**
- * Callback interface for when DTMF has been sent.
+ * Interface to receive callbacks from ITvInputManager regardless of sessions.
+ * @hide
*/
-oneway interface IThirdPartyCallSendDtmfCallback {
- /**
- * Called when the DTMF code has been sent.
- */
- void onSendDtmfCompleted();
+oneway interface ITvInputManagerCallback {
+ void onInputStateChanged(in String inputId, int state);
}
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
index c9484dd..1fdb8c5 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -24,5 +24,5 @@
* @hide
*/
oneway interface ITvInputServiceCallback {
- void onAvailabilityChanged(in String inputId, boolean isAvailable);
+ void onInputStateChanged(int state);
}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 7b8f2ec..5624f3e 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -80,7 +80,7 @@
// Attributes from XML meta data.
private String mSetupActivity;
private String mSettingsActivity;
- private int mType;
+ private int mType = TYPE_VIRTUAL;
/**
* Create a new instance of the TvInputInfo class,
@@ -128,10 +128,13 @@
Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for "
+ si.name);
}
- input.mType = sa.getInt(
- com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
- if (DEBUG) {
- Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+ if (pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE, si.packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ input.mType = sa.getInt(
+ com.android.internal.R.styleable.TvInputService_tvInputType, TYPE_VIRTUAL);
+ if (DEBUG) {
+ Log.d(TAG, "Type loaded. [" + input.mType + "] for " + si.name);
+ }
}
sa.recycle();
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 867b0db..79a83b0 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -24,6 +24,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pools.Pool;
import android.util.Pools.SimplePool;
@@ -37,6 +38,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -65,11 +67,43 @@
*/
public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
+ /**
+ * The TV input is connected.
+ * <p>
+ * State for {@link #getInputState} and {@link
+ * TvInputManager.TvInputListener#onInputStateChanged}.
+ * </p>
+ */
+ public static final int INPUT_STATE_CONNECTED = 0;
+ /**
+ * The TV input is connected but in standby mode. It would take a while until it becomes
+ * fully ready.
+ * <p>
+ * State for {@link #getInputState} and {@link
+ * TvInputManager.TvInputListener#onInputStateChanged}.
+ * </p>
+ */
+ public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
+ /**
+ * The TV input is disconnected.
+ * <p>
+ * State for {@link #getInputState} and {@link
+ * TvInputManager.TvInputListener#onInputStateChanged}.
+ * </p>
+ */
+ public static final int INPUT_STATE_DISCONNECTED = 2;
+
private final ITvInputManager mService;
- // A mapping from an input to the list of its TvInputListenerRecords.
- private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
- new HashMap<String, List<TvInputListenerRecord>>();
+ private final Object mLock = new Object();
+
+ // @GuardedBy(mLock)
+ private final List<TvInputListenerRecord> mTvInputListenerRecordsList =
+ new LinkedList<TvInputListenerRecord>();
+
+ // A mapping from TV input ID to the state of corresponding input.
+ // @GuardedBy(mLock)
+ private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>();
// A mapping from the sequence number of a session to its SessionCallbackRecord.
private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
@@ -81,6 +115,8 @@
private final ITvInputClient mClient;
+ private final ITvInputManagerCallback mCallback;
+
private final int mUserId;
/**
@@ -242,13 +278,17 @@
*/
public abstract static class TvInputListener {
/**
- * This is called when the availability status of a given TV input is changed.
+ * This is called when the state of a given TV input is changed.
*
* @param inputId the id of the TV input.
- * @param isAvailable {@code true} if the given TV input is available to show TV programs.
- * {@code false} otherwise.
+ * @param state state of the TV input. The value is one of the following:
+ * <ul>
+ * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
+ * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
+ * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
+ * </ul>
*/
- public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+ public void onInputStateChanged(String inputId, int state) {
}
}
@@ -265,11 +305,11 @@
return mListener;
}
- public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
+ public void postStateChanged(final String inputId, final int state) {
mHandler.post(new Runnable() {
@Override
public void run() {
- mListener.onAvailabilityChanged(inputId, isAvailable);
+ mListener.onInputStateChanged(inputId, state);
}
});
}
@@ -373,22 +413,23 @@
record.postSessionEvent(eventType, eventArgs);
}
}
-
+ };
+ mCallback = new ITvInputManagerCallback.Stub() {
@Override
- public void onAvailabilityChanged(String inputId, boolean isAvailable) {
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- // Silently ignore - no listener is registered yet.
- return;
- }
- int recordsCount = records.size();
- for (int i = 0; i < recordsCount; i++) {
- records.get(i).postAvailabilityChanged(inputId, isAvailable);
+ public void onInputStateChanged(String inputId, int state) {
+ synchronized (mLock) {
+ mStateMap.put(inputId, state);
+ for (TvInputListenerRecord record : mTvInputListenerRecordsList) {
+ record.postStateChanged(inputId, state);
}
}
}
};
+ try {
+ mService.registerCallback(mCallback, mUserId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "mService.registerCallback failed: " + e);
+ }
}
/**
@@ -405,98 +446,66 @@
}
/**
- * Returns the availability of a given TV input.
+ * Returns the state of a given TV input. It retuns one of the following:
+ * <ul>
+ * <li>{@link #INPUT_STATE_CONNECTED}
+ * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
+ * <li>{@link #INPUT_STATE_DISCONNECTED}
+ * </ul>
*
* @param inputId the id of the TV input.
- * @throws IllegalArgumentException if the argument is {@code null}.
- * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
- * TV input.
+ * @throws IllegalArgumentException if the argument is {@code null} or if there is no
+ * {@link TvInputInfo} corresponding to {@code inputId}.
*/
- public boolean getAvailability(String inputId) {
+ public int getInputState(String inputId) {
if (inputId == null) {
throw new IllegalArgumentException("id cannot be null");
}
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null || records.size() == 0) {
- throw new IllegalStateException("At least one listener should be registered.");
+ synchronized (mLock) {
+ Integer state = mStateMap.get(inputId);
+ if (state == null) {
+ throw new IllegalArgumentException("Unrecognized input ID: " + inputId);
}
- }
- try {
- return mService.getAvailability(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
+ return state.intValue();
}
}
/**
- * Registers a {@link TvInputListener} for a given TV input.
+ * Registers a {@link TvInputListener}.
*
- * @param inputId the id of the TV input.
- * @param listener a listener used to monitor status of the given TV input.
+ * @param listener a listener used to monitor status of the TV inputs.
* @param handler a {@link Handler} that the status change will be delivered to.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
- * @hide
*/
- public void registerListener(String inputId, TvInputListener listener, Handler handler) {
- if (inputId == null) {
- throw new IllegalArgumentException("id cannot be null");
- }
+ public void registerListener(TvInputListener listener, Handler handler) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
if (handler == null) {
throw new IllegalArgumentException("handler cannot be null");
}
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- records = new ArrayList<TvInputListenerRecord>();
- mTvInputListenerRecordsMap.put(inputId, records);
- try {
- mService.registerCallback(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
- records.add(new TvInputListenerRecord(listener, handler));
+ synchronized (mLock) {
+ mTvInputListenerRecordsList.add(new TvInputListenerRecord(listener, handler));
}
}
/**
- * Unregisters the existing {@link TvInputListener} for a given TV input.
+ * Unregisters the existing {@link TvInputListener}.
*
- * @param inputId the id of the TV input.
- * @param listener the existing listener to remove for the given TV input.
+ * @param listener the existing listener to remove.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
- * @hide
*/
- public void unregisterListener(String inputId, final TvInputListener listener) {
- if (inputId == null) {
- throw new IllegalArgumentException("id cannot be null");
- }
+ public void unregisterListener(final TvInputListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- Log.e(TAG, "No listener found for " + inputId);
- return;
- }
- for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
+ synchronized (mLock) {
+ for (Iterator<TvInputListenerRecord> it = mTvInputListenerRecordsList.iterator();
+ it.hasNext(); ) {
TvInputListenerRecord record = it.next();
if (record.getListener() == listener) {
it.remove();
- }
- }
- if (records.isEmpty()) {
- try {
- mService.unregisterCallback(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- mTvInputListenerRecordsMap.remove(inputId);
+ break;
}
}
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index a994f54..3206320 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -82,20 +82,9 @@
*/
public static final String SERVICE_META_DATA = "android.media.tv.input";
- private String mId;
private final Handler mHandler = new ServiceHandler();
private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
new RemoteCallbackList<ITvInputServiceCallback>();
- // STOPSHIP: Redesign the API around the availability change. For now, the service will be
- // always available.
- private final boolean mAvailable = true;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mId = TvInputInfo.generateInputIdForComponentName(
- new ComponentName(getPackageName(), getClass().getName()));
- }
@Override
public final IBinder onBind(Intent intent) {
@@ -104,13 +93,6 @@
public void registerCallback(ITvInputServiceCallback cb) {
if (cb != null) {
mCallbacks.register(cb);
- // The first time a callback is registered, the service needs to report its
- // availability status so that the system can know its initial value.
- try {
- cb.onAvailabilityChanged(mId, mAvailable);
- } catch (RemoteException e) {
- Log.e(TAG, "error in onAvailabilityChanged", e);
- }
}
}
@@ -733,7 +715,6 @@
@SuppressLint("HandlerLeak")
private final class ServiceHandler extends Handler {
private static final int DO_CREATE_SESSION = 1;
- private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
@Override
public final void handleMessage(Message msg) {
@@ -759,20 +740,6 @@
args.recycle();
return;
}
- case DO_BROADCAST_AVAILABILITY_CHANGE: {
- boolean isAvailable = (Boolean) msg.obj;
- int n = mCallbacks.beginBroadcast();
- try {
- for (int i = 0; i < n; i++) {
- mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Unexpected exception", e);
- } finally {
- mCallbacks.finishBroadcast();
- }
- return;
- }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 6a835d6..b7294b8 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -449,13 +449,13 @@
byteBuffer, orderID, nativeByteOrderObj);
env->DeleteLocalRef(me);
me = env->CallObjectMethod(
- byteBuffer, positionID,
- input ? 0 : buffer->offset());
- env->DeleteLocalRef(me);
- me = env->CallObjectMethod(
byteBuffer, limitID,
input ? buffer->capacity() : (buffer->offset() + buffer->size()));
env->DeleteLocalRef(me);
+ me = env->CallObjectMethod(
+ byteBuffer, positionID,
+ input ? 0 : buffer->offset());
+ env->DeleteLocalRef(me);
me = NULL;
env->DeleteLocalRef(nativeByteOrderObj);
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 3dbf77b..52e9910 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -36,6 +36,8 @@
#include <media/stagefright/MetaData.h>
#include <media/stagefright/NuMediaExtractor.h>
+#include <nativehelper/ScopedLocalRef.h>
+
#include "android_util_Binder.h"
namespace android {
@@ -206,12 +208,12 @@
size_t dstSize;
jbyteArray byteArray = NULL;
- if (dst == NULL) {
- jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
- CHECK(byteBufClass != NULL);
+ ScopedLocalRef<jclass> byteBufClass(env, env->FindClass("java/nio/ByteBuffer"));
+ CHECK(byteBufClass.get() != NULL);
+ if (dst == NULL) {
jmethodID arrayID =
- env->GetMethodID(byteBufClass, "array", "()[B");
+ env->GetMethodID(byteBufClass.get(), "array", "()[B");
CHECK(arrayID != NULL);
byteArray =
@@ -251,6 +253,24 @@
*sampleSize = buffer->size();
+ jmethodID positionID = env->GetMethodID(
+ byteBufClass.get(), "position", "(I)Ljava/nio/Buffer;");
+
+ CHECK(positionID != NULL);
+
+ jmethodID limitID = env->GetMethodID(
+ byteBufClass.get(), "limit", "(I)Ljava/nio/Buffer;");
+
+ CHECK(limitID != NULL);
+
+ jobject me = env->CallObjectMethod(
+ byteBuf, limitID, offset + *sampleSize);
+ env->DeleteLocalRef(me);
+ me = env->CallObjectMethod(
+ byteBuf, positionID, offset);
+ env->DeleteLocalRef(me);
+ me = NULL;
+
return OK;
}
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 1685a44..5646740 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -157,6 +157,10 @@
return;
}
sp<Camera> c = get_native_camera(env, camera, NULL);
+ if (c == NULL) {
+ // get_native_camera will throw an exception in this case
+ return;
+ }
sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
process_media_recorder_call(env, mr->setCamera(c->remote(), c->getRecordingProxy()),
"java/lang/RuntimeException", "setCamera failed.");
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index eaf268d..094edf8 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -389,6 +389,7 @@
mSelectedPages = selectedPages;
mSelectedPageCount = PageRangeUtils.getNormalizedPageCount(
mSelectedPages, mDocumentPageCount);
+ updatePreviewAreaAndPageSize();
notifyDataSetChanged();
}
return mSelectedPages;
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index e5606c7..51e2c95 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -19,8 +19,12 @@
<!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
<string name="bugreport_finished_title">Bug report captured</string>
+
+ <!-- Text of notification indicating that swipe left will share the captured bugreport. [CHAR LIMIT=100] -->
+ <string name="bugreport_finished_text" product="watch">Swipe left to share your bug report</string>
<!-- Text of notification indicating that touching will share the captured bugreport. [CHAR LIMIT=100] -->
- <string name="bugreport_finished_text">Touch to share your bug report</string>
+ <string name="bugreport_finished_text" product="default">Touch to share your bug report</string>
+
<!-- Body of dialog informing user about contents of a bugreport. [CHAR LIMIT=NONE] -->
<string name="bugreport_confirm">Bug reports contain data from the system\'s various log files, including personal and private information. Only share bug reports with apps and people you trust.</string>
diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
new file mode 100644
index 0000000..eedae9f
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -0,0 +1,24 @@
+<?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
+ -->
+
+<com.android.systemui.qs.tiles.UserDetail
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <include layout="@layout/user_switcher_host" />
+</com.android.systemui.qs.tiles.UserDetail>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 7e8bfd3..3e4c1f6 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -28,7 +28,7 @@
android:layout_height="@dimen/recents_task_bar_height"
android:layout_gravity="top|center_horizontal"
android:background="@color/recents_task_bar_default_background_color">
- <ImageView
+ <com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/recents_task_view_application_icon_size"
android:layout_height="@dimen/recents_task_view_application_icon_size"
@@ -51,7 +51,7 @@
android:maxLines="2"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
- <ImageView
+ <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/layout/user_switcher_host.xml b/packages/SystemUI/res/layout/user_switcher_host.xml
index 816af57..c1626c6 100644
--- a/packages/SystemUI/res/layout/user_switcher_host.xml
+++ b/packages/SystemUI/res/layout/user_switcher_host.xml
@@ -21,17 +21,11 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#dd000000"
- android:elevation="12dp">
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/volume_panel_top"
- android:background="@*android:drawable/dialog_full_holo_dark">
+ android:layout_height="match_parent">
+
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/user_switcher_item"/>
- </FrameLayout>
+
</com.android.systemui.settings.UserSwitcherHostView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3d53f9c..adab243 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -45,7 +45,7 @@
<color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
<color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
<color name="data_usage_graph_warning">#FFFFFFFF</color>
- <color name="status_bar_clock_color">#33FFFFFF</color>
+ <color name="status_bar_clock_color">#FFFFFFFF</color>
<!-- Tint color for the content on the notification overflow card. -->
<color name="keyguard_overflow_content_color">#ff686868</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b79dbbe..d4feccd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -516,6 +516,10 @@
<string name="quick_settings_time_label">Time</string>
<!-- QuickSettings: User [CHAR LIMIT=NONE] -->
<string name="quick_settings_user_label">Me</string>
+ <!-- QuickSettings: Title of the user detail panel [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_user_title">User</string>
+ <!-- QuickSettings: Label on the item for adding a new user [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_user_new_user">New user</string>
<!-- QuickSettings: Wifi [CHAR LIMIT=NONE] -->
<string name="quick_settings_wifi_label">Wi-Fi</string>
<!-- QuickSettings: Wifi (Not connected) [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 72474b8..5f09cbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -34,6 +34,7 @@
import com.android.systemui.qs.QSTile.DetailAdapter;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSlider;
+import com.android.systemui.statusbar.phone.QSTileHost;
import java.util.ArrayList;
@@ -61,9 +62,10 @@
private boolean mExpanded;
private boolean mListening;
- private TileRecord mDetailRecord;
+ private Record mDetailRecord;
private Callback mCallback;
private BrightnessController mBrightnessController;
+ private QSTileHost mHost;
public QSPanel(Context context) {
this(context, null);
@@ -89,12 +91,24 @@
mBrightnessController = new BrightnessController(getContext(),
(ImageView) findViewById(R.id.brightness_icon),
(ToggleSlider) findViewById(R.id.brightness_slider));
+
+ mDetailDoneButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showDetail(false, mDetailRecord);
+ }
+ });
}
public void setCallback(Callback callback) {
mCallback = callback;
}
+ public void setHost(QSTileHost host) {
+ mHost = host;
+ }
+
+
public void updateResources() {
final Resources res = mContext.getResources();
final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
@@ -143,7 +157,13 @@
}
}
- private void showDetail(boolean show, TileRecord r) {
+ public void showDetailAdapter(boolean show, DetailAdapter adapter) {
+ Record r = new Record();
+ r.detailAdapter = adapter;
+ showDetail(show, r);
+ }
+
+ private void showDetail(boolean show, Record r) {
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
}
@@ -203,40 +223,52 @@
addView(r.tileView);
}
- private void handleShowDetail(TileRecord r, boolean show) {
- if (r == null) return;
- AnimatorListener listener = null;
+ private void handleShowDetail(Record r, boolean show) {
+ if (r instanceof TileRecord) {
+ handleShowDetailTile((TileRecord) r, show);
+ } else {
+ handleShowDetailImpl(r, show, getWidth() /* x */, 0/* y */);
+ }
+ }
+
+ private void handleShowDetailTile(TileRecord r, boolean show) {
+ if ((mDetailRecord != null) == show) return;
+
if (show) {
- if (mDetailRecord != null) return; // already showing something in detail
r.detailAdapter = r.tile.getDetailAdapter();
if (r.detailAdapter == null) return;
- mDetailRecord = r;
- r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
+ }
+ int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
+ int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
+ handleShowDetailImpl(r, show, x, y);
+ }
+
+ private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
+ if ((mDetailRecord != null) == show) return; // already in right state
+ DetailAdapter detailAdapter = null;
+ AnimatorListener listener = null;
+ if (show) {
+ detailAdapter = r.detailAdapter;
+ r.detailView = detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
if (r.detailView == null) throw new IllegalStateException("Must return detail view");
- mDetailDoneButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- showDetail(false, mDetailRecord);
- }
- });
- final Intent settingsIntent = r.detailAdapter.getSettingsIntent();
+
+ final Intent settingsIntent = detailAdapter.getSettingsIntent();
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
mDetailSettingsButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
+ mHost.startSettingsActivity(settingsIntent);
}
});
+
mDetailContent.removeAllViews();
mDetail.bringToFront();
mDetailContent.addView(r.detailView);
+ mDetailRecord = r;
} else {
- if (mDetailRecord == null) return;
listener = mTeardownDetailWhenDone;
}
- fireShowingDetail(show ? r.detailAdapter : null);
- int x = r.tileView.getLeft() + r.tileView.getWidth() / 2;
- int y = r.tileView.getTop() + r.tileView.getHeight() / 2;
+ fireShowingDetail(show ? detailAdapter : null);
mClipper.animateCircularClip(x, y, show, listener);
}
@@ -339,18 +371,21 @@
@Override
public void handleMessage(Message msg) {
if (msg.what == SHOW_DETAIL) {
- handleShowDetail((TileRecord)msg.obj, msg.arg1 != 0);
+ handleShowDetail((Record)msg.obj, msg.arg1 != 0);
} else if (msg.what == SET_TILE_VISIBILITY) {
handleSetTileVisibility((View)msg.obj, msg.arg1 != 0);
}
}
}
- private static final class TileRecord {
- QSTile<?> tile;
- QSTileView tileView;
+ private static class Record {
View detailView;
DetailAdapter detailAdapter;
+ }
+
+ private static final class TileRecord extends Record {
+ QSTile<?> tile;
+ QSTileView tileView;
int row;
int col;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java
new file mode 100644
index 0000000..a9a2724
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetail.java
@@ -0,0 +1,78 @@
+/*
+ * 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.qs.tiles;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+/**
+ * Quick settings detail view for user switching.
+ */
+public class UserDetail extends FrameLayout {
+
+ static final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
+
+ public UserDetail(Context context) {
+ this(context, null);
+ }
+
+ public UserDetail(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public UserDetail(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public UserDetail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public static QSTile.DetailAdapter USER_DETAIL_ADAPTER = new QSTile.DetailAdapter() {
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_user_title;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ return LayoutInflater.from(context).inflate(R.layout.qs_user_detail, parent, false);
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return USER_SETTINGS_INTENT;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index a9a606f..b6d7d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -64,11 +64,9 @@
Context mContext;
SystemServicesProxy mSystemServicesProxy;
-
- // Recents service binding
Handler mHandler;
- boolean mBootCompleted = false;
- boolean mStartAnimationTriggered = false;
+ boolean mBootCompleted;
+ boolean mStartAnimationTriggered;
// Task launching
RecentsConfiguration mConfig;
@@ -95,9 +93,7 @@
}
public void onStart() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|start]");
- }
+ // Do nothing
}
public void onBootCompleted() {
@@ -106,9 +102,6 @@
/** Shows the recents */
public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|showRecents]");
- }
mStatusBarView = statusBarView;
mTriggeredFromAltTab = triggeredFromAltTab;
@@ -121,10 +114,6 @@
/** Hides the recents */
public void onHideRecents(boolean triggeredFromAltTab) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|hideRecents]");
- }
-
if (mBootCompleted) {
if (isRecentsTopMost(getTopMostTask(), null)) {
// Notify recents to hide itself
@@ -139,13 +128,6 @@
/** Toggles the alternate recents activity */
public void onToggleRecents(View statusBarView) {
- if (Console.Enabled) {
- Console.logStartTracingTime(Constants.Log.App.TimeRecentsStartup,
- Constants.Log.App.TimeRecentsStartupKey);
- Console.logStartTracingTime(Constants.Log.App.TimeRecentsLaunchTask,
- Constants.Log.App.TimeRecentsLaunchKey);
- Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|toggleRecents]", "");
- }
mStatusBarView = statusBarView;
mTriggeredFromAltTab = false;
@@ -223,14 +205,6 @@
intent.setPackage(mContext.getPackageName());
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent);
-
- // Time this path
- if (Console.Enabled) {
- Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
- Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents");
- Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
- Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents");
- }
mLastToggleTime = System.currentTimeMillis();
return;
} else {
@@ -395,11 +369,6 @@
startAlternateRecentsActivity(topTask, opts, null);
}
}
-
- if (Console.Enabled) {
- Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
- Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity");
- }
mLastToggleTime = System.currentTimeMillis();
}
@@ -417,10 +386,9 @@
intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
intent.putExtra(EXTRA_TRIGGERED_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
if (opts != null) {
- mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
- UserHandle.USER_CURRENT));
+ mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else {
- mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index e7ac2e1..c49e244 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -52,43 +52,6 @@
}
}
- public static class Log {
- public static class App {
- public static final String TimeRecentsStartupKey = "startup";
- public static final String TimeRecentsLaunchKey = "launchTask";
- public static final String TimeRecentsScreenshotTransitionKey = "screenshot";
- public static final boolean TimeRecentsStartup = false;
- public static final boolean TimeRecentsLaunchTask = false;
- public static final boolean TimeRecentsScreenshotTransition = false;
-
-
- public static final boolean RecentsComponent = false;
- public static final boolean TaskDataLoader = false;
- public static final boolean SystemUIHandshake = false;
- public static final boolean TimeSystemCalls = false;
- public static final boolean Memory = false;
- public static final boolean Search = false;
- }
-
- public static class UI {
- public static final boolean Draw = false;
- public static final boolean ClickEvents = false;
- public static final boolean TouchEvents = false;
- public static final boolean MeasureAndLayout = false;
- public static final boolean HwLayers = false;
- public static final boolean Focus = false;
- }
-
- public static class TaskStack {
- public static final boolean SynchronizeViewsWithModel = false;
- }
-
- public static class ViewPool {
- public static final boolean PoolCallbacks = false;
- }
- }
-
- /** XXX: We are going to move almost all of these into a resource once they are nailed down. */
public static class Values {
public static class App {
public static int AppWidgetHostId = 1024;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 1e581c1..29a0262 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -52,16 +52,23 @@
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
-/* Activity */
+/**
+ * The main Recents activity that is started from AlternateRecentsComponent.
+ */
public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
+ // Actions and Extras sent from AlternateRecentsComponent
final static String EXTRA_TRIGGERED_FROM_ALT_TAB = "extra_triggered_from_alt_tab";
final static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
final static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
+ RecentsConfiguration mConfig;
+ boolean mVisible;
+
+ // Top level views
RecentsView mRecentsView;
SystemBarScrimViews mScrimViews;
ViewStub mEmptyViewStub;
@@ -69,29 +76,29 @@
ViewStub mFullscreenOverlayStub;
FullscreenTransitionOverlayView mFullScreenOverlayView;
- RecentsConfiguration mConfig;
-
+ // Search AppWidget
RecentsAppWidgetHost mAppWidgetHost;
AppWidgetProviderInfo mSearchAppWidgetInfo;
AppWidgetHostView mSearchAppWidgetHostView;
- boolean mVisible;
// Runnables to finish the Recents activity
- FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true);
+ FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable();
FinishRecentsRunnable mFinishLaunchHomeRunnable;
/**
- * A Runnable to finish Recents either with/without a transition, and either by calling finish()
- * or just launching the specified intent.
+ * A common Runnable to finish Recents either by calling finish() (with a custom animation) or
+ * launching Home with some ActivityOptions. Generally we always launch home when we exit
+ * Recents rather than just finishing the activity since we don't know what is behind Recents in
+ * the task stack. The only case where we finish() directly is when we are cancelling the full
+ * screen transition from the app.
*/
class FinishRecentsRunnable implements Runnable {
- boolean mUseCustomFinishTransition;
Intent mLaunchIntent;
ActivityOptions mLaunchOpts;
- public FinishRecentsRunnable(boolean withTransition) {
- mUseCustomFinishTransition = withTransition;
+ public FinishRecentsRunnable() {
+ // Do nothing
}
/**
@@ -111,77 +118,66 @@
// Finish Recents
if (mLaunchIntent != null) {
if (mLaunchOpts != null) {
- startActivityAsUser(mLaunchIntent, new UserHandle(UserHandle.USER_CURRENT));
+ startActivityAsUser(mLaunchIntent, UserHandle.CURRENT);
} else {
- startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(),
- new UserHandle(UserHandle.USER_CURRENT));
+ startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), UserHandle.CURRENT);
}
} else {
finish();
- if (mUseCustomFinishTransition) {
- overridePendingTransition(R.anim.recents_to_launcher_enter,
- R.anim.recents_to_launcher_exit);
- }
+ overridePendingTransition(R.anim.recents_to_launcher_enter,
+ R.anim.recents_to_launcher_exit);
}
}
}
- // Broadcast receiver to handle messages from AlternateRecentsComponent
+ /**
+ * Broadcast receiver to handle messages from AlternateRecentsComponent.
+ */
final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake,
- "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed);
- }
if (action.equals(ACTION_HIDE_RECENTS_ACTIVITY)) {
if (intent.getBooleanExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
- // Dismiss recents, launching the focused task
- dismissRecentsIfVisible();
+ // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
+ dismissRecentsToFocusedTaskOrHome(false);
} else {
- // If we are mid-animation into Recents, then reverse it and finish
- if (mFullScreenOverlayView == null ||
- !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
- // Otherwise, either finish Recents, or launch Home directly
- ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
- null, mFinishLaunchHomeRunnable, null);
- mRecentsView.startExitToHomeAnimation(
- new ViewAnimation.TaskViewExitContext(exitTrigger));
- }
+ // Otherwise, dismiss Recents to Home
+ dismissRecentsToHome(true);
}
} else if (action.equals(ACTION_TOGGLE_RECENTS_ACTIVITY)) {
- // Try and unfilter and filtered stacks
- if (!mRecentsView.unfilterFilteredStacks()) {
- // If there are no filtered stacks, dismiss recents and launch the first task
- dismissRecentsIfVisible();
- }
+ // If we are toggling Recents, then first unfilter any filtered stacks first
+ dismissRecentsToFocusedTaskOrHome(true);
} else if (action.equals(ACTION_START_ENTER_ANIMATION)) {
// Try and start the enter animation (or restart it on configuration changed)
ReferenceCountedTrigger t = new ReferenceCountedTrigger(context, null, null, null);
mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
mFullScreenOverlayView, t));
- // Call our callback
onEnterAnimationTriggered();
}
}
};
- // Broadcast receiver to handle messages from the system
+ /**
+ * Broadcast receiver to handle messages from the system
+ */
final BroadcastReceiver mSystemBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action.equals(Intent.ACTION_SCREEN_OFF) && mVisible) {
- mFinishLaunchHomeRunnable.run();
+ if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ // When the screen turns off, dismiss Recents to Home
+ dismissRecentsToHome(false);
} else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
- // Refresh the search widget
+ // When the search activity changes, update the Search widget
refreshSearchWidget();
}
}
};
- // Debug trigger
+ /**
+ * A custom debug trigger to listen for a debug key chord.
+ */
final DebugTrigger mDebugTrigger = new DebugTrigger(new Runnable() {
@Override
public void run() {
@@ -211,15 +207,17 @@
mConfig.launchedToTaskId = launchIntent.getIntExtra(
AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_TASK_ID, -1);
- // Add the default no-recents layout
- if (mEmptyView == null) {
- mEmptyView = mEmptyViewStub.inflate();
- }
+ // Update the top level view's visibilities
if (mConfig.launchedWithNoRecentTasks) {
+ if (mEmptyView == null) {
+ mEmptyView = mEmptyViewStub.inflate();
+ }
mEmptyView.setVisibility(View.VISIBLE);
mRecentsView.setSearchBarVisibility(View.GONE);
} else {
- mEmptyView.setVisibility(View.GONE);
+ if (mEmptyView != null) {
+ mEmptyView.setVisibility(View.GONE);
+ }
if (mRecentsView.hasSearchBar()) {
mRecentsView.setSearchBarVisibility(View.VISIBLE);
} else {
@@ -227,7 +225,7 @@
}
}
- // Show the scrim if we animate into Recents without window transitions
+ // Animate the SystemUI scrims into view
mScrimViews.prepareEnterRecentsAnimation();
}
@@ -250,12 +248,6 @@
ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
appWidgetId = -1;
}
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake,
- "[RecentsActivity|onCreate|settings|appWidgetId]",
- "Id: " + appWidgetId,
- Console.AnsiBlue);
- }
}
// If there is no id, then bind a new search app widget
@@ -263,13 +255,6 @@
Pair<Integer, AppWidgetProviderInfo> widgetInfo =
ssp.bindSearchAppWidget(mAppWidgetHost);
if (widgetInfo != null) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake,
- "[RecentsActivity|onCreate|searchWidget]",
- "Id: " + widgetInfo.first + " Info: " + widgetInfo.second,
- Console.AnsiBlue);
- }
-
// Save the app widget id into the settings
mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first);
mSearchAppWidgetInfo = widgetInfo.second;
@@ -283,12 +268,6 @@
if (Constants.DebugFlags.App.EnableSearchLayout) {
int appWidgetId = mConfig.searchBarAppWidgetId;
if (appWidgetId >= 0) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake,
- "[RecentsActivity|onCreate|addSearchAppWidgetView]",
- "Id: " + appWidgetId,
- Console.AnsiBlue);
- }
mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId,
mSearchAppWidgetInfo);
Bundle opts = new Bundle();
@@ -305,28 +284,50 @@
}
/** Dismisses recents if we are already visible and the intent is to toggle the recents view */
- boolean dismissRecentsIfVisible() {
+ boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
if (mVisible) {
- // If we are mid-animation into Recents, then reverse it and finish
- if (mFullScreenOverlayView == null ||
- !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
- // If we have a focused task, then launch that task
- if (!mRecentsView.launchFocusedTask()) {
- if (mConfig.launchedFromHome) {
- // Just start the animation out of recents
- ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
- null, mFinishLaunchHomeRunnable, null);
- mRecentsView.startExitToHomeAnimation(
- new ViewAnimation.TaskViewExitContext(exitTrigger));
- } else {
- // Otherwise, try and launch the first task
- if (!mRecentsView.launchFirstTask()) {
- // If there are no tasks, then just finish recents
- mFinishLaunchHomeRunnable.run();
- }
- }
- }
+ // If we are mid-animation into Recents, reverse the animation now
+ if (mFullScreenOverlayView != null &&
+ mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) return true;
+ // If we currently have filtered stacks, then unfilter those first
+ if (checkFilteredStackState &&
+ mRecentsView.unfilterFilteredStacks()) return true;
+ // If we have a focused Task, launch that Task now
+ if (mRecentsView.launchFocusedTask()) return true;
+ // If we launched from Home, then return to Home
+ if (mConfig.launchedFromHome) {
+ dismissRecentsToHomeRaw(true);
+ return true;
}
+ // Otherwise, try and return to the first Task in the stack
+ if (mRecentsView.launchFirstTask()) return true;
+ // If none of the other cases apply, then just go Home
+ dismissRecentsToHomeRaw(true);
+ return true;
+ }
+ return false;
+ }
+
+ /** Dismisses Recents directly to Home. */
+ void dismissRecentsToHomeRaw(boolean animated) {
+ if (animated) {
+ ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+ null, mFinishLaunchHomeRunnable, null);
+ mRecentsView.startExitToHomeAnimation(
+ new ViewAnimation.TaskViewExitContext(exitTrigger));
+ } else {
+ mFinishLaunchHomeRunnable.run();
+ }
+ }
+
+ /** Dismisses Recents directly to Home if we currently aren't transitioning. */
+ boolean dismissRecentsToHome(boolean animated) {
+ if (mVisible) {
+ // If we are mid-animation into Recents, reverse the animation now
+ if (mFullScreenOverlayView != null &&
+ mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) return true;
+ // Return to Home
+ dismissRecentsToHomeRaw(animated);
return true;
}
return false;
@@ -336,13 +337,6 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (Console.Enabled) {
- Console.logDivider(Constants.Log.App.SystemUIHandshake);
- Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onCreate]",
- getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed);
- Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
- Constants.Log.App.TimeRecentsStartupKey, "onCreate");
- }
// Initialize the loader and the configuration
RecentsTaskLoader.initialize(this);
@@ -410,11 +404,13 @@
}
void onConfigurationChange() {
+ // Update RecentsConfiguration
+ mConfig = RecentsConfiguration.reinitialize(this);
+
// Try and start the enter animation (or restart it on configuration changed)
ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null);
mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(
mFullScreenOverlayView, t));
- // Call our callback
onEnterAnimationTriggered();
}
@@ -423,18 +419,6 @@
super.onNewIntent(intent);
setIntent(intent);
- if (Console.Enabled) {
- Console.logDivider(Constants.Log.App.SystemUIHandshake);
- Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]",
- intent.getAction() + " visible: " + mVisible, Console.AnsiRed);
- Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
- Constants.Log.App.TimeRecentsStartupKey, "onNewIntent");
- }
-
- // Initialize the loader and the configuration
- RecentsTaskLoader.initialize(this);
- mConfig = RecentsConfiguration.reinitialize(this);
-
// Update the recent tasks
updateRecentsTasks(intent);
@@ -446,10 +430,6 @@
@Override
protected void onStart() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStart]", "",
- Console.AnsiRed);
- }
super.onStart();
// Register the broadcast receiver to handle messages from our service
@@ -462,10 +442,6 @@
@Override
protected void onResume() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onResume]", "",
- Console.AnsiRed);
- }
super.onResume();
// Start listening for widget package changes if there is one bound, post it since we don't
@@ -484,64 +460,28 @@
}, 1);
}
+ // Mark Recents as visible
mVisible = true;
}
@Override
- public void onAttachedToWindow() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake,
- "[RecentsActivity|onAttachedToWindow]", "",
- Console.AnsiRed);
- }
- super.onAttachedToWindow();
- }
-
- @Override
- public void onDetachedFromWindow() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake,
- "[RecentsActivity|onDetachedFromWindow]", "",
- Console.AnsiRed);
- }
- super.onDetachedFromWindow();
- }
-
- @Override
- protected void onPause() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onPause]", "",
- Console.AnsiRed);
- }
- super.onPause();
- }
-
- @Override
protected void onStop() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStop]", "",
- Console.AnsiRed);
- }
super.onStop();
// Unregister the RecentsService receiver
unregisterReceiver(mServiceBroadcastReceiver);
// Stop listening for widget package changes if there was one bound
- if (mConfig.searchBarAppWidgetId >= 0) {
+ if (mAppWidgetHost.isListening()) {
mAppWidgetHost.stopListening();
}
}
@Override
protected void onDestroy() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onDestroy]", "",
- Console.AnsiRed);
- }
super.onDestroy();
- // Unregister the screen off receiver
+ // Unregister the system broadcast receivers
unregisterReceiver(mSystemBroadcastReceiver);
RecentsTaskLoader.getInstance().unregisterReceivers();
}
@@ -583,26 +523,8 @@
// Test mode where back does not do anything
if (mConfig.debugModeEnabled) return;
- // If we are mid-animation into Recents, then reverse it and finish
- if (mFullScreenOverlayView == null ||
- !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
- // If we are currently filtering in any stacks, unfilter them first
- if (!mRecentsView.unfilterFilteredStacks()) {
- if (mConfig.launchedFromHome) {
- // Just start the animation out of recents
- ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
- null, mFinishLaunchHomeRunnable, null);
- mRecentsView.startExitToHomeAnimation(
- new ViewAnimation.TaskViewExitContext(exitTrigger));
- } else {
- // Otherwise, try and launch the first task
- if (!mRecentsView.launchFirstTask()) {
- // If there are no tasks, then just finish recents
- mFinishLaunchHomeRunnable.run();
- }
- }
- }
- }
+ // Dismiss Recents to the focused Task or Home
+ dismissRecentsToFocusedTaskOrHome(true);
}
/** Called when debug mode is triggered */
@@ -623,7 +545,7 @@
/** Called when the enter recents animation is triggered. */
public void onEnterAnimationTriggered() {
- // Animate the scrims in
+ // Animate the SystemUI scrim views
mScrimViews.startEnterRecentsAnimation();
}
@@ -644,7 +566,7 @@
@Override
public void onExitToHomeAnimationTriggered() {
- // Animate the scrims out
+ // Animate the SystemUI scrim views out
mScrimViews.startExitRecentsAnimation();
}
@@ -664,7 +586,6 @@
@Override
public void refreshSearchWidget() {
- // Load the Search widget again
bindSearchBarAppWidget();
addSearchBarAppWidgetView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index 43d7a54..a63e167 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -33,6 +33,7 @@
Context mContext;
RecentsAppWidgetHostCallbacks mCb;
RecentsConfiguration mConfig;
+ boolean mIsListening;
public RecentsAppWidgetHost(Context context, int hostId) {
super(context, hostId);
@@ -42,6 +43,7 @@
public void startListening(RecentsAppWidgetHostCallbacks cb) {
mCb = cb;
+ mIsListening = true;
super.startListening();
}
@@ -51,6 +53,11 @@
// Ensure that we release any references to the callbacks
mCb = null;
mContext = null;
+ mIsListening = false;
+ }
+
+ public boolean isListening() {
+ return mIsListening;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index e62d989..439765e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -35,6 +35,7 @@
* NOTE: We should not hold any references to a Context from a static instance */
public class RecentsConfiguration {
static RecentsConfiguration sInstance;
+ static int sPrevConfigurationHashCode;
DisplayMetrics mDisplayMetrics;
@@ -138,7 +139,11 @@
if (sInstance == null) {
sInstance = new RecentsConfiguration(context);
}
- sInstance.update(context);
+ int configHashCode = context.getResources().getConfiguration().hashCode();
+ if (sPrevConfigurationHashCode != configHashCode) {
+ sInstance.update(context);
+ sPrevConfigurationHashCode = configHashCode;
+ }
return sInstance;
}
@@ -179,10 +184,8 @@
transposeRecentsLayoutWithOrientation =
res.getBoolean(R.bool.recents_transpose_layout_with_orientation);
- // Search bar
+ // Search Bar
searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
-
- // Update the search widget id
searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
// Task stack
@@ -242,12 +245,6 @@
// Nav bar scrim
navBarScrimEnterDuration =
res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
-
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.MeasureAndLayout,
- "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait",
- Console.AnsiGreen);
- }
}
/** Updates the system insets */
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 b8beda6f..ced4043 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -31,6 +31,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -80,6 +81,8 @@
ComponentName mAssistComponent;
Bitmap mDummyIcon;
+ int mDummyThumbnailWidth;
+ int mDummyThumbnailHeight;
Paint mBgProtectionPaint;
Canvas mBgProtectionCanvas;
@@ -96,6 +99,13 @@
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
+ // Get the dummy thumbnail width/heights
+ Resources res = context.getResources();
+ int wId = com.android.internal.R.dimen.thumbnail_width;
+ int hId = com.android.internal.R.dimen.thumbnail_height;
+ mDummyThumbnailWidth = res.getDimensionPixelSize(wId);
+ mDummyThumbnailHeight = res.getDimensionPixelSize(hId);
+
// Create the protection paints
mBgProtectionPaint = new Paint();
mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
@@ -213,7 +223,8 @@
// If we are mocking, then just return a dummy thumbnail
if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
- Bitmap thumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight,
+ Bitmap.Config.ARGB_8888);
thumbnail.eraseColor(0xff333333);
return thumbnail;
}
@@ -239,6 +250,8 @@
*/
public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
+ if (taskThumbnail == null) return null;
+
Bitmap thumbnail = taskThumbnail.mainThumbnail;
ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
if (thumbnail == null && descriptor != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 2f1c1c4..31011ae 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -25,7 +25,6 @@
import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
* The package monitor listens for changes from PackageManager to update the contents of the Recents
@@ -33,7 +32,7 @@
*/
public class RecentsPackageMonitor extends PackageMonitor {
public interface PackageCallbacks {
- public void onComponentRemoved(Set<ComponentName> cns);
+ public void onComponentRemoved(HashSet<ComponentName> cns);
}
PackageCallbacks mCb;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index cbb8892..71979c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -29,7 +29,6 @@
import android.os.UserHandle;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
import java.util.Collections;
@@ -44,9 +43,6 @@
/** Adds a new task to the load queue */
void addTask(Task t) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, " [TaskResourceLoadQueue|addTask]");
- }
if (!mQueue.contains(t)) {
mQueue.add(t);
}
@@ -60,25 +56,16 @@
* force reloaded.
*/
Task nextTask() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, " [TaskResourceLoadQueue|nextTask]");
- }
return mQueue.poll();
}
/** Removes a task from the load queue */
void removeTask(Task t) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, " [TaskResourceLoadQueue|removeTask]");
- }
mQueue.remove(t);
}
/** Clears all the tasks from the load queue */
void clearTasks() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, " [TaskResourceLoadQueue|clearTasks]");
- }
mQueue.clear();
}
@@ -124,9 +111,6 @@
/** Restarts the loader thread */
void start(Context context) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|start]");
- }
mContext = context;
mCancelled = false;
mSystemServicesProxy = new SystemServicesProxy(context);
@@ -138,9 +122,6 @@
/** Requests the loader thread to stop after the current iteration */
void stop() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|stop]");
- }
// Mark as cancelled for the thread to pick up
mCancelled = true;
mSystemServicesProxy = null;
@@ -154,25 +135,13 @@
@Override
public void run() {
while (true) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
- }
if (mCancelled) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[TaskResourceLoader|cancel|" + Thread.currentThread().getId() + "]");
- }
// We have to unset the context here, since the background thread may be using it
// when we call stop()
mContext = null;
// If we are cancelled, then wait until we are started again
synchronized(mLoadThread) {
try {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[TaskResourceLoader|waitOnLoadThreadCancelled]");
- }
mLoadThread.wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
@@ -186,11 +155,6 @@
if (t != null) {
Drawable cachedIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
Bitmap cachedThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- " [TaskResourceLoader|load]",
- t + " icon: " + cachedIcon + " thumbnail: " + cachedThumbnail);
- }
// Load the application icon if it is stale or we haven't cached one yet
if (cachedIcon == null) {
Drawable icon = null;
@@ -198,10 +162,6 @@
t.userId);
if (info != null) {
icon = ssp.getActivityIcon(info, t.userId);
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- " [TaskResourceLoader|loadedIcon]", icon);
- }
}
// If we can't load the icon, then set the default application icon into the
// cache. This will remain until the task's last active time is updated.
@@ -213,10 +173,6 @@
Bitmap thumbnail = ssp.getTaskThumbnail(t.key.id);
if (thumbnail != null) {
thumbnail.setHasAlpha(false);
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- " [TaskResourceLoader|loadedThumbnail]", thumbnail);
- }
}
// Even if we can't load the icon, we set the default thumbnail into the
// cache. This will remain until the task's last active time is updated.
@@ -240,10 +196,6 @@
if (!mCancelled && mLoadQueue.isEmpty()) {
synchronized(mLoadQueue) {
try {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[TaskResourceLoader|waitOnLoadQueue]");
- }
mWaitingOnLoadQueue = true;
mLoadQueue.wait();
mWaitingOnLoadQueue = false;
@@ -290,12 +242,6 @@
int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
mMaxThumbnailCacheSize;
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
- " iconCache: " + iconCacheSize);
- }
-
// Create the default assets
Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
icon.eraseColor(0x00000000);
@@ -315,13 +261,6 @@
mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
mDefaultThumbnail, mDefaultApplicationIcon);
-
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[RecentsTaskLoader|defaultBitmaps]",
- "icon: " + mDefaultApplicationIcon +
- " default thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
- }
}
/** Initializes the recents task loader */
@@ -343,29 +282,15 @@
}
private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp) {
- long t1 = System.currentTimeMillis();
-
List<ActivityManager.RecentTaskInfo> tasks =
ssp.getRecentTasks(50, UserHandle.CURRENT.getIdentifier());
Collections.reverse(tasks);
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TimeSystemCalls,
- "[RecentsTaskLoader|getRecentTasks]",
- "" + (System.currentTimeMillis() - t1) + "ms");
- Console.log(Constants.Log.App.TaskDataLoader,
- "[RecentsTaskLoader|tasks]", "" + tasks.size());
- }
return tasks;
}
/** Reload the set of recent tasks */
public SpaceNode reload(Context context, int preloadCount) {
- long t1 = System.currentTimeMillis();
-
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
- }
RecentsConfiguration config = RecentsConfiguration.getInstance();
Resources res = context.getResources();
LinkedHashSet<Task> tasksToLoad = new LinkedHashSet<Task>();
@@ -378,7 +303,6 @@
List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
// Add each task to the task stack
- t1 = System.currentTimeMillis();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = tasks.get(i);
@@ -408,12 +332,6 @@
// Preload the specified number of apps
if (i >= (taskCount - preloadCount)) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[RecentsTaskLoader|preloadTask]",
- "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
- }
-
// Load the icon from the cache if possible
task.applicationIcon = mApplicationIconCache.getCheckLastActiveTime(task.key);
if (task.applicationIcon == null) {
@@ -436,10 +354,6 @@
// Load the thumbnail (if possible and not the foremost task, from the cache)
task.thumbnail = mThumbnailCache.getCheckLastActiveTime(task.key);
if (task.thumbnail == null) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[RecentsTaskLoader|loadingTaskThumbnail]");
- }
if (isForemostTask) {
// We force loading the thumbnail icon for the foremost task
task.thumbnail = ssp.getTaskThumbnail(task.key.id);
@@ -460,17 +374,8 @@
}
// Add the task to the stack
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
- }
stack.addTask(task);
}
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TimeSystemCalls,
- "[RecentsTaskLoader|getAllTaskTopThumbnail]",
- "" + (System.currentTimeMillis() - t1) + "ms");
- }
// Simulate the groupings that we describe
stack.createAffiliatedGroupings();
@@ -512,12 +417,6 @@
Drawable applicationIcon = mApplicationIconCache.get(t.key);
Bitmap thumbnail = mThumbnailCache.get(t.key);
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
- t + " applicationIcon: " + applicationIcon + " thumbnail: " + thumbnail +
- " thumbnailCacheSize: " + mThumbnailCache.size());
- }
-
boolean requiresLoad = false;
if (applicationIcon == null) {
applicationIcon = mDefaultApplicationIcon;
@@ -535,23 +434,12 @@
/** Releases the task resource data back into the pool. */
public void unloadTaskData(Task t) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[RecentsTaskLoader|unloadTask]", t +
- " thumbnailCacheSize: " + mThumbnailCache.size());
- }
-
mLoadQueue.removeTask(t);
t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
}
/** Completely removes the resource data from the pool. */
public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader,
- "[RecentsTaskLoader|deleteTask]", t);
- }
-
mLoadQueue.removeTask(t);
mThumbnailCache.remove(t.key);
mApplicationIconCache.remove(t.key);
@@ -562,9 +450,6 @@
/** Stops the task loader and clears all pending tasks */
void stopLoader() {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
- }
mLoader.stop();
mLoadQueue.clearTasks();
}
@@ -585,11 +470,6 @@
* out of memory.
*/
public void onTrimMemory(int level) {
- if (Console.Enabled) {
- Console.log(Constants.Log.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
- Console.trimMemoryLevelToString(level));
- }
-
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
// Stop the loader immediately when the UI is no longer visible
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 e3bcff0..13fbe64 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -204,8 +204,8 @@
removeGroup(group);
}
// Update the lock-to-app state
- Task newFrontMostTask = getFrontMostTask();
t.canLockToTask = false;
+ Task newFrontMostTask = getFrontMostTask();
if (newFrontMostTask != null) {
newFrontMostTask.canLockToTask = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
new file mode 100644
index 0000000..3adee0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java
@@ -0,0 +1,82 @@
+/*
+ * 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.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * This is an optimized ImageView that does not trigger a requestLayout() or invalidate() when
+ * setting the image to Null.
+ */
+public class FixedSizeImageView extends ImageView {
+
+ int mFixedWidth;
+ int mFixedHeight;
+ boolean mAllowRelayout = true;
+ boolean mAllowInvalidate = true;
+
+ public FixedSizeImageView(Context context) {
+ this(context, null);
+ }
+
+ public FixedSizeImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mFixedWidth = getMeasuredWidth();
+ mFixedHeight = getMeasuredHeight();
+ }
+
+ @Override
+ public void requestLayout() {
+ if (mAllowRelayout) {
+ super.requestLayout();
+ }
+ }
+
+ @Override
+ public void invalidate() {
+ if (mAllowInvalidate) {
+ super.invalidate();
+ }
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ if (drawable == null || (mFixedWidth > 0 && mFixedHeight > 0)) {
+ mAllowRelayout = false;
+ mAllowInvalidate = false;
+ }
+ super.setImageDrawable(drawable);
+ mAllowRelayout = true;
+ mAllowInvalidate = true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
index 57b8ea4..63f59be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
@@ -22,8 +22,6 @@
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -34,9 +32,7 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.Console;
/**
@@ -152,11 +148,6 @@
public void prepareAnimateOnEnterRecents(Bitmap screenshot) {
if (!mConfig.launchedFromAppWithScreenshot) return;
- if (Console.Enabled) {
- Console.logStartTracingTime(Constants.Log.App.TimeRecentsScreenshotTransition,
- Constants.Log.App.TimeRecentsScreenshotTransitionKey);
- }
-
setClipTop(0);
setClipBottom(getMeasuredHeight());
setDim(0);
@@ -180,11 +171,6 @@
/** Animates this view as it enters recents */
public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx,
final Runnable postAnimRunnable) {
- if (Console.Enabled) {
- Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition,
- Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Starting");
- }
-
// Cancel the current animation
if (mEnterAnimation != null) {
mEnterAnimation.removeAllListeners();
@@ -226,11 +212,6 @@
mCb.onEnterAnimationComplete();
// Run the given post-anim runnable
postAnimRunnable.run();
-
- if (Console.Enabled) {
- Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition,
- Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Completed");
- }
}
});
}
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 73bbf86..b32d3dd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -45,7 +45,7 @@
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
-import java.util.Set;
+import java.util.HashSet;
/**
* This view is the the top level layout that contains TaskStacks (which are laid out according
@@ -141,20 +141,12 @@
TaskView tv = (TaskView) stackView.getChildAt(j);
Task task = tv.getTask();
if (tv.isFocusedTask()) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
- "Found focused Task");
- }
onTaskViewClicked(stackView, tv, stack, task, false);
return true;
}
}
}
}
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
- "No Tasks focused");
- }
return false;
}
@@ -228,12 +220,6 @@
if (searchBar != null) {
mSearchBar = searchBar;
addView(mSearchBar);
-
- if (Console.Enabled) {
- Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsView|setSearchBar]",
- "" + (mSearchBar.getVisibility() == View.VISIBLE),
- Console.AnsiBlue);
- }
}
}
}
@@ -260,13 +246,6 @@
int height = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|measure]",
- "width: " + width + " height: " + height, Console.AnsiGreen);
- Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
- Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onMeasure");
- }
-
// Get the search bar bounds and measure the search bar layout
if (mSearchBar != null) {
Rect searchBarSpaceBounds = new Rect();
@@ -303,13 +282,6 @@
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.MeasureAndLayout, "[RecentsView|layout]",
- new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen);
- Console.logTraceTime(Constants.Log.App.TimeRecentsStartup,
- Constants.Log.App.TimeRecentsStartupKey, "RecentsView.onLayout");
- }
-
// Get the search bar bounds so that we lay it out
if (mSearchBar != null) {
Rect searchBarSpaceBounds = new Rect();
@@ -351,11 +323,6 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.MeasureAndLayout,
- "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
- }
-
// Update the configuration with the latest system insets and trigger a relayout
mConfig.updateSystemInsets(insets.getSystemWindowInsets());
requestLayout();
@@ -482,11 +449,6 @@
final Runnable launchRunnable = new Runnable() {
@Override
public void run() {
- if (Console.Enabled) {
- Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
- Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity");
- }
-
if (task.isActive) {
// Bring an active task to the foreground
RecentsTaskLoader.getInstance().getSystemServicesProxy()
@@ -515,19 +477,9 @@
// And clean up the old task
onTaskViewDismissed(task);
}
-
- if (Console.Enabled) {
- Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
- Constants.Log.App.TimeRecentsLaunchKey, "startActivity");
- }
}
};
- if (Console.Enabled) {
- Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask,
- Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched");
- }
-
// Launch the app right away if there is no task view, otherwise, animate the icon out first
if (tv == null) {
post(launchRunnable);
@@ -599,7 +551,7 @@
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
- public void onComponentRemoved(Set<ComponentName> cns) {
+ public void onComponentRemoved(HashSet<ComponentName> cns) {
// Propagate this event down to each task stack view
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
index 8409227..e0298ab 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -28,8 +28,6 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.LinearInterpolator;
-import com.android.systemui.recents.misc.Console;
-import com.android.systemui.recents.Constants;
/**
* This class facilitates swipe to dismiss. It defines an interface to be implemented by the
@@ -178,11 +176,6 @@
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.TouchEvents,
- "[SwipeHelper|interceptTouchEvent]",
- Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
- }
final int action = ev.getAction();
switch (action) {
@@ -294,12 +287,6 @@
}
public boolean onTouchEvent(MotionEvent ev) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.TouchEvents,
- "[SwipeHelper|touchEvent]",
- Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
- }
-
if (!mDragging) {
if (!onInterceptTouchEvent(ev)) {
return mCanCurrViewBeDimissed;
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 adc808a..0b35f59 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -23,9 +23,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -45,7 +44,7 @@
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Set;
+import java.util.HashSet;
/* The visual representation of a task stack view */
@@ -83,6 +82,7 @@
int mFocusedTaskIndex = -1;
OverScroller mScroller;
ObjectAnimator mScrollAnimator;
+ boolean mEnableStackClipping = true;
// Optimizations
ReferenceCountedTrigger mHwLayersTrigger;
@@ -167,12 +167,9 @@
requestSynchronizeStackViewsWithModel(0);
}
void requestSynchronizeStackViewsWithModel(int duration) {
- if (Console.Enabled) {
- Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
- "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
- }
if (!mStackViewsDirty) {
invalidate(mStackAlgorithm.mStackRect);
+ mStackViewsDirty = true;
}
if (mAwaitingFirstLayout) {
// Skip the animation if we are awaiting first layout
@@ -180,7 +177,6 @@
} else {
mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
}
- mStackViewsDirty = true;
}
/** Returns a mapping of child view to Task. */
@@ -266,11 +262,6 @@
if (visibleRangeOut != null) {
visibleRangeOut[0] = frontMostVisibleIndex;
visibleRangeOut[1] = backMostVisibleIndex;
- if (Console.Enabled) {
- Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
- "[TaskStackView|updateStackTransforms]",
- "Back: " + backMostVisibleIndex + " Front: " + frontMostVisibleIndex);
- }
}
}
@@ -290,11 +281,6 @@
/** Synchronizes the views with the model */
void synchronizeStackViewsWithModel() {
- if (Console.Enabled) {
- Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
- "[TaskStackView|synchronizeViewsWithModel]",
- "mStackViewsDirty: " + mStackViewsDirty, Console.AnsiYellow);
- }
if (mStackViewsDirty) {
// Get all the task transforms
ArrayList<Task> tasks = mStack.getTasks();
@@ -344,11 +330,6 @@
mStackViewsAnimationDuration);
}
- if (Console.Enabled) {
- Console.log(Constants.Log.TaskStack.SynchronizeViewsWithModel,
- " [TaskStackView|viewChildren]", "" + getChildCount());
- }
-
mStackViewsAnimationDuration = 0;
mStackViewsDirty = false;
}
@@ -357,7 +338,7 @@
/** Updates the clip for each of the task views. */
void clipTaskViews() {
// Update the clip on each task child
- if (Constants.DebugFlags.App.EnableTaskStackClipping) {
+ if (Constants.DebugFlags.App.EnableTaskStackClipping && mEnableStackClipping) {
int childCount = getChildCount();
for (int i = 0; i < childCount - 1; i++) {
TaskView tv = (TaskView) getChildAt(i);
@@ -379,10 +360,12 @@
// stacked and we can make assumptions about the visibility of the this
// task relative to the ones in front of it.
if (nextTv != null) {
- // XXX: Can hash the visible rects for this run
+ // We calculate the bottom clip independent of the footer (since we animate
+ // that)
+ int scaledMaxFooterHeight = (int) (tv.getMaxFooterHeight() * tv.getScaleX());
tv.getHitRect(mTmpRect);
nextTv.getHitRect(mTmpRect2);
- clipBottom = (mTmpRect.bottom - mTmpRect2.top);
+ clipBottom = (mTmpRect.bottom - scaledMaxFooterHeight - mTmpRect2.top);
}
}
tv.setClipFromBottom(clipBottom);
@@ -395,6 +378,18 @@
}
}
+ /** Enables/Disables clipping of the tasks in the stack. */
+ void setStackClippingEnabled(boolean stackClippingEnabled) {
+ if (!stackClippingEnabled) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ tv.setClipFromBottom(0);
+ }
+ }
+ mEnableStackClipping = stackClippingEnabled;
+ }
+
/** Sets the current stack scroll */
public void setStackScroll(int value) {
mStackScroll = value;
@@ -537,11 +532,6 @@
mMaxScroll = mStackAlgorithm.mMaxScroll;
// Debug logging
- if (Constants.Log.UI.MeasureAndLayout) {
- Console.log(" [TaskStack|minScroll] " + mMinScroll);
- Console.log(" [TaskStack|maxScroll] " + mMaxScroll);
- }
-
if (boundScrollToNewMinMax) {
boundScroll();
}
@@ -563,9 +553,6 @@
/** Focuses the task at the specified index in the stack */
void focusTask(int taskIndex, boolean scrollToNewPosition) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "" + taskIndex);
- }
if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
mFocusedTaskIndex = taskIndex;
@@ -575,9 +562,6 @@
Runnable postScrollRunnable = null;
if (tv != null) {
tv.setFocusedTask();
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]", "Requesting focus");
- }
} else {
postScrollRunnable = new Runnable() {
@Override
@@ -586,10 +570,6 @@
TaskView tv = getChildViewForTask(t);
if (tv != null) {
tv.setFocusedTask();
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusTask]",
- "Requesting focus after scroll animation");
- }
}
}
};
@@ -611,11 +591,6 @@
/** Focuses the next task in the stack */
void focusNextTask(boolean forward) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Focus, "[TaskStackView|focusNextTask]", "" +
- mFocusedTaskIndex);
- }
-
// Find the next index to focus
int numTasks = mStack.getTaskCount();
if (mFocusedTaskIndex < 0) {
@@ -630,24 +605,12 @@
/** Enables the hw layers and increments the hw layer requirement ref count */
void addHwLayersRefCount(String reason) {
- if (Console.Enabled) {
- int refCount = mHwLayersTrigger.getCount();
- Console.log(Constants.Log.UI.HwLayers,
- "[TaskStackView|addHwLayersRefCount] refCount: " +
- refCount + "->" + (refCount + 1) + " " + reason);
- }
mHwLayersTrigger.increment();
}
/** Decrements the hw layer requirement ref count and disables the hw layers when we don't
need them anymore. */
void decHwLayersRefCount(String reason) {
- if (Console.Enabled) {
- int refCount = mHwLayersTrigger.getCount();
- Console.log(Constants.Log.UI.HwLayers,
- "[TaskStackView|decHwLayersRefCount] refCount: " +
- refCount + "->" + (refCount - 1) + " " + reason);
- }
mHwLayersTrigger.decrement();
}
@@ -665,7 +628,6 @@
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
setStackScroll(mScroller.getCurrY());
- invalidate(mStackAlgorithm.mStackRect);
// If we just finished scrolling, then disable the hw layers
if (mScroller.isFinished()) {
@@ -676,10 +638,6 @@
@Override
public void dispatchDraw(Canvas canvas) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
- Console.AnsiPurple);
- }
synchronizeStackViewsWithModel();
clipTaskViews();
super.dispatchDraw(canvas);
@@ -703,25 +661,12 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|measure]",
- "width: " + width + " height: " + height +
- " awaitingFirstLayout: " + mAwaitingFirstLayout, Console.AnsiGreen);
- }
// Compute our stack/task rects
Rect taskStackBounds = new Rect();
mConfig.getTaskStackBounds(width, height, taskStackBounds);
computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom);
- // Debug logging
- if (Constants.Log.UI.MeasureAndLayout) {
- Console.log(" [TaskStack|fullRect] " + mStackAlgorithm.mRect);
- Console.log(" [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
- Console.log(" [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
- Console.log(" [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
- }
-
// If this is the first layout, then scroll to the front of the stack and synchronize the
// stack views immediately
if (mAwaitingFirstLayout) {
@@ -736,7 +681,7 @@
TaskView t = (TaskView) getChildAt(i);
t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
- mConfig.taskViewLockToAppButtonHeight, MeasureSpec.EXACTLY));
+ t.getMaxFooterHeight(), MeasureSpec.EXACTLY));
}
setMeasuredDimension(width, height);
@@ -749,26 +694,13 @@
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.MeasureAndLayout, "[TaskStackView|layout]",
- "" + new Rect(left, top, right, bottom), Console.AnsiGreen);
- }
-
- // Debug logging
- if (Constants.Log.UI.MeasureAndLayout) {
- Console.log(" [TaskStack|fullRect] " + mStackAlgorithm.mRect);
- Console.log(" [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
- Console.log(" [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
- Console.log(" [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
- }
-
// Layout each of the children
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView t = (TaskView) getChildAt(i);
t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
- mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
+ mStackAlgorithm.mTaskRect.height() + t.getMaxFooterHeight());
}
if (mAwaitingFirstLayout) {
@@ -1021,19 +953,12 @@
@Override
public TaskView createView(Context context) {
- if (Console.Enabled) {
- Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|createPoolView]");
- }
return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
}
@Override
public void prepareViewToEnterPool(TaskView tv) {
Task task = tv.getTask();
- if (Console.Enabled) {
- Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
- tv.getTask() + " tv: " + tv);
- }
// Report that this tasks's data is no longer being used
RecentsTaskLoader.getInstance().unloadTaskData(task);
@@ -1050,11 +975,6 @@
@Override
public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
- if (Console.Enabled) {
- Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
- "isNewView: " + isNewView);
- }
-
// Rebind the task and request that this task's data be filled into the TaskView
tv.onTaskBound(task);
RecentsTaskLoader.getInstance().loadTaskData(task);
@@ -1083,10 +1003,6 @@
}
// Add/attach the view to the hierarchy
- if (Console.Enabled) {
- Console.log(Constants.Log.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]",
- "" + insertIndex);
- }
if (isNewView) {
addView(tv, insertIndex);
@@ -1112,11 +1028,6 @@
@Override
public void onTaskViewAppIconClicked(TaskView tv) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Icon]",
- tv.getTask() + " is currently filtered: " + mStack.hasFilteredTasks(),
- Console.AnsiCyan);
- }
if (Constants.DebugFlags.App.EnableTaskFiltering) {
if (mStack.hasFilteredTasks()) {
mStack.unfilterTasks();
@@ -1135,11 +1046,6 @@
@Override
public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
- task + " cb: " + mCb);
- }
-
// Cancel any doze triggers
mUIDozeTrigger.stopDozing();
@@ -1163,7 +1069,7 @@
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
- public void onComponentRemoved(Set<ComponentName> cns) {
+ public void onComponentRemoved(HashSet<ComponentName> cns) {
// For other tasks, just remove them directly if they no longer exist
ArrayList<Task> tasks = mStack.getTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 9c48896..65407a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -146,7 +146,8 @@
transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
// Set the alphas
- transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
+ // transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
+ transformOut.dismissAlpha = 1f;
// Update the rect and visibility
transformOut.rect.set(mTaskRect);
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 15ace13..bd4ea90 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -22,7 +22,6 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
-import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.Constants;
/* Handles touch events for a TaskStackView. */
@@ -100,12 +99,6 @@
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.TouchEvents,
- "[TaskStackViewTouchHandler|interceptTouchEvent]",
- Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
- }
-
// Return early if we have no children
boolean hasChildren = (mSv.getChildCount() > 0);
if (!hasChildren) {
@@ -186,12 +179,6 @@
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.TouchEvents,
- "[TaskStackViewTouchHandler|touchEvent]",
- Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
- }
-
// Short circuit if we have no children
boolean hasChildren = (mSv.getChildCount() > 0);
if (!hasChildren) {
@@ -290,16 +277,6 @@
int overscrollRange = (int) (Math.min(1f,
Math.abs((float) velocity / mMaximumVelocity)) *
Constants.Values.TaskStackView.TaskStackOverscrollRange);
-
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.TouchEvents,
- "[TaskStackViewTouchHandler|fling]",
- "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
- " maxVelocity: " + mMaximumVelocity +
- " overscrollRange: " + overscrollRange,
- Console.AnsiGreen);
- }
-
// Fling scroll
mSv.mScroller.fling(0, mSv.getStackScroll(),
0, -velocity,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
index 636746d..08a25f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
@@ -21,12 +21,11 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.ImageView;
import com.android.systemui.recents.model.Task;
/** The task thumbnail view */
-public class TaskThumbnailView extends ImageView {
+public class TaskThumbnailView extends FixedSizeImageView {
Task mTask;
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 ab14863..199d3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -175,11 +175,6 @@
/** Synchronizes this view's properties with the task's transform */
void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]",
- "duration: " + duration, Console.AnsiPurple);
- }
-
// Update the bar view
mBarView.updateViewPropertiesToTaskTransform(toTransform, duration);
@@ -500,7 +495,7 @@
mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
}
- /** Sets the stubbed state of this task view.
+ /** Sets the stubbed state of this task view. */
void setStubState(boolean isStub) {
if (!mIsStub && isStub) {
// This is now a stub task view, so clip to the bar height, hide the thumbnail
@@ -513,7 +508,7 @@
mThumbnailView.setVisibility(View.VISIBLE);
}
mIsStub = isStub;
- } */
+ }
/**
* Returns whether this view should be clipped, or any views below should clip against this
@@ -554,9 +549,19 @@
return mFooterHeight;
}
+ /** Gets the max footer height. */
+ public int getMaxFooterHeight() {
+ return mMaxFooterHeight;
+ }
+
/** Animates the footer into and out of view. */
public void animateFooterVisibility(boolean visible, int duration, int delay) {
- if (!mTask.canLockToTask) return;
+ if (!mTask.canLockToTask) {
+ if (mLockToAppButtonView.getVisibility() == View.VISIBLE) {
+ mLockToAppButtonView.setVisibility(View.INVISIBLE);
+ }
+ return;
+ }
if (mMaxFooterHeight <= 0) return;
if (mFooterAnimator != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
index a3b10f2..a5c5862 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java
@@ -90,6 +90,7 @@
mListView = (ListView) findViewById(android.R.id.list);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(this);
+ refreshUsers();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 0f12274..d32ad50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -18,29 +18,22 @@
import android.content.Context;
import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import com.android.systemui.R;
-import com.android.systemui.settings.UserSwitcherHostView;
-import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.tiles.UserDetail;
/**
* Container for image of the multi user switcher (tappable).
*/
public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
- private ViewGroup mOverlayParent;
+ private QSPanel mQsPanel;
public MultiUserSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -52,25 +45,15 @@
setOnClickListener(this);
}
- public void setOverlayParent(ViewGroup parent) {
- mOverlayParent = parent;
+ public void setQsPanel(QSPanel qsPanel) {
+ mQsPanel = qsPanel;
}
@Override
public void onClick(View v) {
final UserManager um = UserManager.get(getContext());
if (um.isUserSwitcherEnabled()) {
- final UserSwitcherHostView switcher =
- (UserSwitcherHostView) LayoutInflater.from(getContext()).inflate(
- R.layout.user_switcher_host, mOverlayParent, false);
- switcher.setFinishRunnable(new Runnable() {
- @Override
- public void run() {
- mOverlayParent.removeView(switcher);
- }
- });
- switcher.refreshUsers();
- mOverlayParent.addView(switcher);
+ mQsPanel.showDetailAdapter(true, UserDetail.USER_DETAIL_ADAPTER);
} else {
Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
getContext(), v, ContactsContract.Profile.CONTENT_URI,
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 55b3088..fc0f2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -150,7 +150,6 @@
super.onFinishInflate();
mHeader = (StatusBarHeaderView) findViewById(R.id.header);
mHeader.setOnClickListener(this);
- mHeader.setOverlayParent(this);
mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
mQsContainer = findViewById(R.id.quick_settings_container);
mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
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 505af44..2c43161 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -699,6 +699,7 @@
mBluetoothController, mLocationController, mRotationLockController,
mNetworkController, mZenModeController, null /*tethering*/,
mCastController, mVolumeComponent, mFlashlightController);
+ mQSPanel.setHost(qsh);
for (QSTile<?> tile : qsh.getTiles()) {
mQSPanel.addTile(tile);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 33d1b15..dc06ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -475,10 +475,6 @@
});
}
- public void setOverlayParent(ViewGroup parent) {
- mMultiUserSwitch.setOverlayParent(parent);
- }
-
@Override
public void onClick(View v) {
if (v == mSettingsButton) {
@@ -501,6 +497,7 @@
if (mQSPanel != null) {
mQSPanel.setCallback(mQsPanelCallback);
}
+ mMultiUserSwitch.setQsPanel(qsp);
}
@Override
@@ -589,7 +586,7 @@
mQsDetailHeader.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- detail.setToggleState(!toggleState);
+ detail.setToggleState(!mQsDetailHeaderSwitch.isChecked());
}
});
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index adfa1f2..f431fdb 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -49,6 +49,9 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionLegacyHelper;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -205,6 +208,8 @@
private Drawable mBackgroundDrawable;
+ private float mElevation;
+
private int mFrameResource = 0;
private int mTextColor = 0;
@@ -224,6 +229,7 @@
private boolean mClosingActionMenu;
private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
+ private MediaController mMediaController;
private AudioManager mAudioManager;
private KeyguardManager mKeyguardManager;
@@ -1688,15 +1694,42 @@
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_DOWN: {
+ int direction = keyCode == KeyEvent.KEYCODE_VOLUME_UP ? AudioManager.ADJUST_RAISE
+ : AudioManager.ADJUST_LOWER;
+ // If we have a session send it the volume command, otherwise
+ // use the suggested stream.
+ if (mMediaController != null) {
+ mMediaController.adjustVolumeBy(direction, AudioManager.FLAG_SHOW_UI);
+ } else {
+ MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
+ mVolumeControlStreamType, direction, AudioManager.FLAG_SHOW_UI);
+ }
+ return true;
+ }
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- // Similar code is in PhoneFallbackEventHandler in case the window
- // doesn't have one of these. In this case, we execute it here and
- // eat the event instead, because we have mVolumeControlStreamType
- // and they don't.
getAudioManager().handleKeyDown(event, mVolumeControlStreamType);
return true;
}
+ // These are all the recognized media key codes in
+ // KeyEvent.isMediaKey()
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ if (mMediaController != null) {
+ if (mMediaController.dispatchMediaButtonEvent(event)) {
+ return true;
+ }
+ }
+ }
case KeyEvent.KEYCODE_MENU: {
onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
@@ -1750,7 +1783,19 @@
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_DOWN: {
+ // If we have a session send it the volume command, otherwise
+ // use the suggested stream.
+ if (mMediaController != null) {
+ mMediaController.adjustVolumeBy(0, AudioManager.FLAG_PLAY_SOUND
+ | AudioManager.FLAG_VIBRATE);
+ } else {
+ MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
+ mVolumeControlStreamType, 0,
+ AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE);
+ }
+ return true;
+ }
case KeyEvent.KEYCODE_VOLUME_MUTE: {
// Similar code is in PhoneFallbackEventHandler in case the window
// doesn't have one of these. In this case, we execute it here and
@@ -1759,6 +1804,25 @@
getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
return true;
}
+ // These are all the recognized media key codes in
+ // KeyEvent.isMediaKey()
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ if (mMediaController != null) {
+ if (mMediaController.dispatchMediaButtonEvent(event)) {
+ return true;
+ }
+ }
+ }
case KeyEvent.KEYCODE_MENU: {
onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
@@ -3189,6 +3253,7 @@
+ Integer.toHexString(mFrameResource));
}
}
+ mElevation = a.getDimension(com.android.internal.R.styleable.Window_windowElevation, 0);
mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
}
@@ -3278,28 +3343,31 @@
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
- Drawable drawable = mBackgroundDrawable;
+ final Drawable background;
if (mBackgroundResource != 0) {
- drawable = getContext().getDrawable(mBackgroundResource);
+ background = getContext().getDrawable(mBackgroundResource);
+ } else {
+ background = mBackgroundDrawable;
}
- mDecor.setWindowBackground(drawable);
- drawable = null;
+ mDecor.setWindowBackground(background);
+
+ final Drawable frame;
if (mFrameResource != 0) {
- drawable = getContext().getDrawable(mFrameResource);
+ frame = getContext().getDrawable(mFrameResource);
+ } else {
+ frame = null;
}
- mDecor.setWindowFrame(drawable);
+ mDecor.setWindowFrame(frame);
- // System.out.println("Text=" + Integer.toHexString(mTextColor) +
- // " Sel=" + Integer.toHexString(mTextSelectedColor) +
- // " Title=" + Integer.toHexString(mTitleColor));
-
- if (mTitleColor == 0) {
- mTitleColor = mTextColor;
- }
+ mDecor.setElevation(mElevation);
if (mTitle != null) {
setTitle(mTitle);
}
+
+ if (mTitleColor == 0) {
+ mTitleColor = mTextColor;
+ }
setTitleColor(mTitleColor);
}
@@ -3773,6 +3841,16 @@
return mVolumeControlStreamType;
}
+ @Override
+ public void setMediaController(MediaController controller) {
+ mMediaController = controller;
+ }
+
+ @Override
+ public MediaController getMediaController() {
+ return mMediaController;
+ }
+
private boolean isTranslucent() {
TypedArray a = getWindowStyle();
return a.getBoolean(a.getResourceId(
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index ef15a80..608aa44 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -250,6 +250,9 @@
// Vibrator pattern for a short vibration when tapping on an hour/minute tick of a Clock.
long[] mClockTickVibePattern;
+ // Vibrator pattern for a short vibration when tapping on a day/month/year date of a Calendar.
+ long[] mCalendarDateVibePattern;
+
// Vibrator pattern for haptic feedback during boot when safe mode is disabled.
long[] mSafeModeDisabledVibePattern;
@@ -1071,6 +1074,8 @@
com.android.internal.R.array.config_keyboardTapVibePattern);
mClockTickVibePattern = getLongIntArray(mContext.getResources(),
com.android.internal.R.array.config_clockTickVibePattern);
+ mCalendarDateVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_calendarDateVibePattern);
mSafeModeDisabledVibePattern = getLongIntArray(mContext.getResources(),
com.android.internal.R.array.config_safeModeDisabledVibePattern);
mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
@@ -5406,6 +5411,9 @@
case HapticFeedbackConstants.CLOCK_TICK:
pattern = mClockTickVibePattern;
break;
+ case HapticFeedbackConstants.CALENDAR_DATE:
+ pattern = mCalendarDateVibePattern;
+ break;
case HapticFeedbackConstants.SAFE_MODE_DISABLED:
pattern = mSafeModeDisabledVibePattern;
break;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 5bfde4d..c3a9dbe 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3922,6 +3922,11 @@
break;
}
+ // Is it a *file* we need to drop?
+ if (!isRestorableFile(info)) {
+ okay = false;
+ }
+
// If the policy is satisfied, go ahead and set up to pipe the
// data to the agent.
if (DEBUG && okay && mAgent != null) {
@@ -4082,9 +4087,9 @@
}
}
- // Problems setting up the agent communication, or an already-
- // ignored package: skip to the next tar stream entry by
- // reading and discarding this file.
+ // Problems setting up the agent communication, an explicitly
+ // dropped file, or an already-ignored package: skip to the
+ // next stream entry by reading and discarding this file.
if (!okay) {
if (DEBUG) Slog.d(TAG, "[discarding file content]");
long bytesToConsume = (info.size + 511) & ~511;
@@ -4691,6 +4696,31 @@
return info;
}
+ private boolean isRestorableFile(FileMetadata info) {
+ if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Dropping cache file path " + info.path);
+ }
+ return false;
+ }
+
+ if (FullBackup.ROOT_TREE_TOKEN.equals(info.domain)) {
+ // It's possible this is "no-backup" dir contents in an archive stream
+ // produced on a device running a version of the OS that predates that
+ // API. Respect the no-backup intention and don't let the data get to
+ // the app.
+ if (info.path.startsWith("no_backup/")) {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Dropping no_backup file path " + info.path);
+ }
+ return false;
+ }
+ }
+
+ // Otherwise we think this file is good to go
+ return true;
+ }
+
private void HEXLOG(byte[] block) {
int offset = 0;
int todo = block.length;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 6554ed3..86f8777 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3199,13 +3199,17 @@
break;
}
case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
- NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
- if (nai == null) {
- loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
- break;
+ if (msg.arg1 == 0) {
+ setProvNotificationVisibleIntent(false, msg.arg2, 0, null, null);
+ } else {
+ NetworkAgentInfo nai = mNetworkForNetId.get(msg.arg2);
+ if (nai == null) {
+ loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
+ break;
+ }
+ setProvNotificationVisibleIntent(true, msg.arg2, nai.networkInfo.getType(),
+ nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj);
}
- setProvNotificationVisibleIntent(msg.arg1 != 0, nai.networkInfo.getType(),
- nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj);
break;
}
case NetworkStateTracker.EVENT_STATE_CHANGED: {
@@ -4958,10 +4962,19 @@
break;
}
}
- setProvNotificationVisibleIntent(visible, networkType, extraInfo, pendingIntent);
+ // Concatenate the range of types onto the range of NetIDs.
+ int id = MAX_NET_ID + 1 + (networkType - ConnectivityManager.TYPE_NONE);
+ setProvNotificationVisibleIntent(visible, id, networkType, extraInfo, pendingIntent);
}
- private void setProvNotificationVisibleIntent(boolean visible, int networkType,
+ /**
+ * Show or hide network provisioning notificaitons.
+ *
+ * @param id an identifier that uniquely identifies this notification. This must match
+ * between show and hide calls. We use the NetID value but for legacy callers
+ * we concatenate the range of types with the range of NetIDs.
+ */
+ private void setProvNotificationVisibleIntent(boolean visible, int id, int networkType,
String extraInfo, PendingIntent intent) {
if (DBG) {
log("setProvNotificationVisibleIntent: E visible=" + visible + " networkType=" +
@@ -5008,14 +5021,14 @@
notification.contentIntent = intent;
try {
- notificationManager.notify(NOTIFICATION_ID, networkType, notification);
+ notificationManager.notify(NOTIFICATION_ID, id, notification);
} catch (NullPointerException npe) {
loge("setNotificaitionVisible: visible notificationManager npe=" + npe);
npe.printStackTrace();
}
} else {
try {
- notificationManager.cancel(NOTIFICATION_ID, networkType);
+ notificationManager.cancel(NOTIFICATION_ID, id);
} catch (NullPointerException npe) {
loge("setNotificaitionVisible: cancel notificationManager npe=" + npe);
npe.printStackTrace();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cdb3835..79cb60e 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1189,6 +1189,9 @@
*/
private boolean mUserIsMonkey;
+ /** Flag whether the device has a recents UI */
+ final boolean mHasRecents;
+
final ServiceThread mHandlerThread;
final MainHandler mHandler;
@@ -1808,10 +1811,18 @@
break;
}
case SYSTEM_USER_START_MSG: {
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+ Integer.toString(msg.arg1), msg.arg1);
mSystemServiceManager.startUser(msg.arg1);
break;
}
case SYSTEM_USER_CURRENT_MSG: {
+ mBatteryStatsService.noteEvent(
+ BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
+ Integer.toString(msg.arg2), msg.arg2);
+ mBatteryStatsService.noteEvent(
+ BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+ Integer.toString(msg.arg1), msg.arg1);
mSystemServiceManager.switchUser(msg.arg1);
break;
}
@@ -2205,6 +2216,9 @@
mConfigurationSeq = mConfiguration.seq = 1;
mProcessCpuTracker.init();
+ mHasRecents = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hasRecents);
+
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mStackSupervisor = new ActivityStackSupervisor(this);
@@ -5649,6 +5663,11 @@
}
@Override
+ public final void notifyLaunchTaskBehindComplete(IBinder token) {
+ mStackSupervisor.scheduleLaunchTaskBehindComplete(token);
+ }
+
+ @Override
public String getCallingPackage(IBinder token) {
synchronized (this) {
ActivityRecord r = getCallingRecordLocked(token);
@@ -10157,7 +10176,11 @@
}
if (goingCallback != null) goingCallback.run();
-
+
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+ Integer.toString(mCurrentUserId), mCurrentUserId);
+ mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+ Integer.toString(mCurrentUserId), mCurrentUserId);
mSystemServiceManager.startUser(mCurrentUserId);
synchronized (this) {
@@ -12430,12 +12453,11 @@
pw.print(" lastCachedPss="); pw.println(r.lastCachedPss);
pw.print(prefix);
pw.print(" ");
- pw.print("keeping="); pw.print(r.keeping);
- pw.print(" cached="); pw.print(r.cached);
+ pw.print("cached="); pw.print(r.cached);
pw.print(" empty="); pw.print(r.empty);
pw.print(" hasAboveClient="); pw.println(r.hasAboveClient);
- if (!r.keeping) {
+ if (r.setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
if (r.lastWakeTime != 0) {
long wtime;
BatteryStatsImpl stats = service.mBatteryStatsService.getActiveStatistics();
@@ -15189,7 +15211,6 @@
app.adjSeq = mAdjSeq;
app.curRawAdj = app.maxAdj;
app.foregroundActivities = false;
- app.keeping = true;
app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT;
// System processes can do UI, and when they do we want to have
@@ -15213,7 +15234,6 @@
return (app.curAdj=app.maxAdj);
}
- app.keeping = false;
app.systemNoUi = false;
// Determine the importance of the process, starting with most
@@ -15458,9 +15478,6 @@
app.adjType = "cch-started-services";
}
}
- // Don't kill this process because it is doing work; it
- // has said it is doing work.
- app.keeping = true;
}
for (int conni = s.connections.size()-1;
conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
@@ -15550,9 +15567,6 @@
if (!client.cached) {
app.cached = false;
}
- if (client.keeping) {
- app.keeping = true;
- }
adjType = "service";
}
}
@@ -15664,7 +15678,6 @@
app.adjType = "provider";
}
app.cached &= client.cached;
- app.keeping |= client.keeping;
app.adjTypeCode = ActivityManager.RunningAppProcessInfo
.REASON_PROVIDER_IN_USE;
app.adjSource = client;
@@ -15708,7 +15721,6 @@
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
- app.keeping = true;
app.adjType = "provider";
app.adjTarget = cpr.name;
}
@@ -15791,9 +15803,6 @@
schedGroup = Process.THREAD_GROUP_DEFAULT;
}
}
- if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
- app.keeping = true;
- }
// Do final modification to adj. Everything we do between here and applying
// the final setAdj must be done in this function, because we will also use
@@ -16015,7 +16024,7 @@
while (i > 0) {
i--;
ProcessRecord app = mLruProcesses.get(i);
- if (!app.keeping) {
+ if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
long wtime;
synchronized (stats) {
wtime = stats.getProcessWakeTime(app.info.uid,
@@ -16060,7 +16069,7 @@
+ " during " + realtimeSince);
app.baseProcessTracker.reportExcessiveWake(app.pkgList);
} else if (doCpuKills && uptimeSince > 0
- && ((cputimeUsed*100)/uptimeSince) >= 50) {
+ && ((cputimeUsed*100)/uptimeSince) >= 25) {
synchronized (stats) {
stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
uptimeSince, cputimeUsed);
@@ -16076,23 +16085,11 @@
}
}
- private final boolean applyOomAdjLocked(ProcessRecord app, boolean wasKeeping,
+ private final boolean applyOomAdjLocked(ProcessRecord app,
ProcessRecord TOP_APP, boolean doingAll, long now) {
boolean success = true;
if (app.curRawAdj != app.setRawAdj) {
- if (wasKeeping && !app.keeping) {
- // This app is no longer something we want to keep. Note
- // its current wake lock time to later know to kill it if
- // it is not behaving well.
- BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
- synchronized (stats) {
- app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
- app.pid, SystemClock.elapsedRealtime());
- }
- app.lastCpuTime = app.curCpuTime;
- }
-
app.setRawAdj = app.curRawAdj;
}
@@ -16181,6 +16178,21 @@
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
"Proc state change of " + app.processName
+ " to " + app.curProcState);
+ boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
+ boolean curImportant = app.curProcState < ActivityManager.PROCESS_STATE_SERVICE;
+ if (setImportant && !curImportant) {
+ // This app is no longer something we consider important enough to allow to
+ // use arbitrary amounts of battery power. Note
+ // its current wake lock time to later know to kill it if
+ // it is not behaving well.
+ BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+ synchronized (stats) {
+ app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
+ app.pid, SystemClock.elapsedRealtime());
+ }
+ app.lastCpuTime = app.curCpuTime;
+
+ }
app.setProcState = app.curProcState;
if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
app.notCachedSinceIdle = false;
@@ -16257,11 +16269,9 @@
return false;
}
- final boolean wasKeeping = app.keeping;
-
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
- return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll, now);
+ return applyOomAdjLocked(app, TOP_APP, doingAll, now);
}
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
@@ -16417,7 +16427,6 @@
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null) {
app.procStateChanged = false;
- final boolean wasKeeping = app.keeping;
computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
// If we haven't yet assigned the final cached adj
@@ -16472,7 +16481,7 @@
}
}
- applyOomAdjLocked(app, wasKeeping, TOP_APP, true, now);
+ applyOomAdjLocked(app, TOP_APP, true, now);
// Count the number of process types.
switch (app.curProcState) {
@@ -17116,7 +17125,8 @@
}
if (foreground) {
- mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId));
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+ oldUserId));
mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
@@ -17491,6 +17501,9 @@
}
uss.mState = UserStartedState.STATE_SHUTDOWN;
}
+ mBatteryStatsService.noteEvent(
+ BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
+ Integer.toString(userId), userId);
mSystemServiceManager.stopUser(userId);
broadcastIntentLocked(null, null, shutdownIntent,
null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 46521c5..6c47922 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -167,6 +167,8 @@
ActivityContainer mInitialActivityContainer;
TaskDescription taskDescription; // the recents information for this activity
+ boolean mLaunchTaskBehind; // this activity is actively being launched with
+ // ActivityOptions.setLaunchTaskBehind, will be cleared once launch is completed.
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
@@ -400,6 +402,7 @@
mInitialActivityContainer = container;
if (options != null) {
pendingOptions = new ActivityOptions(options);
+ mLaunchTaskBehind = pendingOptions.getLaunchTaskBehind();
}
// This starts out true, since the initial state of an activity
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1f92bf9..32f2624 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -825,7 +825,7 @@
prev.task.touchActiveTime();
clearLaunchTime(prev);
final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
- if (next == null || next.noDisplay || next.task != prev.task) {
+ if (mService.mHasRecents && (next == null || next.noDisplay || next.task != prev.task)) {
prev.updateThumbnail(screenshotActivities(prev), null);
}
stopFullyDrawnTraceIfNeeded();
@@ -1081,6 +1081,7 @@
if (next == mLastScreenshotActivity) {
invalidateLastScreenshot();
}
+ mReturningActivityOptions = null;
}
private void setVisibile(ActivityRecord r, boolean visible) {
@@ -1134,19 +1135,15 @@
return true;
}
- final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
- ActivityRecord r = topRunningActivityLocked(null);
- if (r != null) {
- ensureActivitiesVisibleLocked(r, starting, null, configChanges);
- }
- }
-
/**
* Make sure that all activities that need to be visible (that is, they
* currently can be seen by the user) actually are.
*/
- final void ensureActivitiesVisibleLocked(ActivityRecord top, ActivityRecord starting,
- String onlyThisProcess, int configChanges) {
+ final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
+ ActivityRecord top = topRunningActivityLocked(null);
+ if (top == null) {
+ return;
+ }
if (DEBUG_VISBILITY) Slog.v(
TAG, "ensureActivitiesVisible behind " + top
+ " configChanges=0x" + Integer.toHexString(configChanges));
@@ -1178,37 +1175,34 @@
continue;
}
aboveTop = false;
- if (!behindFullscreen) {
+ // mLaunchingBehind: Activities launching behind are at the back of the task stack
+ // but must be drawn initially for the animation as though they were visible.
+ if (!behindFullscreen || r.mLaunchTaskBehind) {
if (DEBUG_VISBILITY) Slog.v(
TAG, "Make visible? " + r + " finishing=" + r.finishing
+ " state=" + r.state);
- final boolean doThisProcess = onlyThisProcess == null
- || onlyThisProcess.equals(r.processName);
-
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
- if (r != starting && doThisProcess) {
+ if (r != starting) {
ensureActivityConfigurationLocked(r, 0);
}
if (r.app == null || r.app.thread == null) {
- if (onlyThisProcess == null || onlyThisProcess.equals(r.processName)) {
- // This activity needs to be visible, but isn't even
- // running... get it started, but don't resume it
- // at this point.
- if (DEBUG_VISBILITY) Slog.v(TAG, "Start and freeze screen for " + r);
- if (r != starting) {
- r.startFreezingScreenLocked(r.app, configChanges);
- }
- if (!r.visible) {
- if (DEBUG_VISBILITY) Slog.v(
- TAG, "Starting and making visible: " + r);
- setVisibile(r, true);
- }
- if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(r, false, false);
- }
+ // This activity needs to be visible, but isn't even
+ // running... get it started, but don't resume it
+ // at this point.
+ if (DEBUG_VISBILITY) Slog.v(TAG, "Start and freeze screen for " + r);
+ if (r != starting) {
+ r.startFreezingScreenLocked(r.app, configChanges);
+ }
+ if (!r.visible || r.mLaunchTaskBehind) {
+ if (DEBUG_VISBILITY) Slog.v(
+ TAG, "Starting and making visible: " + r);
+ setVisibile(r, true);
+ }
+ if (r != starting) {
+ mStackSupervisor.startSpecificActivityLocked(r, false, false);
}
} else if (r.visible) {
@@ -1217,17 +1211,14 @@
if (DEBUG_VISBILITY) Slog.v(TAG, "Skipping: already visible at " + r);
r.stopFreezingScreenLocked(false);
try {
- if (mReturningActivityOptions != null) {
- if (activityNdx > 0) {
- ActivityRecord under = activities.get(activityNdx - 1);
- under.app.thread.scheduleOnNewActivityOptions(under.appToken,
- mReturningActivityOptions);
- }
- mReturningActivityOptions = null;
+ if (mReturningActivityOptions != null && r == top && activityNdx > 0) {
+ ActivityRecord under = activities.get(activityNdx - 1);
+ under.app.thread.scheduleOnNewActivityOptions(under.appToken,
+ mReturningActivityOptions);
}
} catch(RemoteException e) {
}
- } else if (onlyThisProcess == null) {
+ } else {
// This activity is not currently visible, but is running.
// Tell it to become visible.
r.visible = true;
@@ -1650,7 +1641,9 @@
} else {
mWindowManager.prepareAppTransition(prev.task == next.task
? AppTransition.TRANSIT_ACTIVITY_OPEN
- : AppTransition.TRANSIT_TASK_OPEN, false);
+ : next.mLaunchTaskBehind
+ ? AppTransition.TRANSIT_TASK_OPEN_BEHIND
+ : AppTransition.TRANSIT_TASK_OPEN, false);
}
}
if (false) {
@@ -1842,8 +1835,11 @@
ActivityStack lastStack = mStackSupervisor.getLastStack();
final boolean fromHome = lastStack.isHomeStack();
if (!isHomeStack() && (fromHome || topTask() != task)) {
- task.setTaskToReturnTo(fromHome ?
- lastStack.topTask().taskType : APPLICATION_ACTIVITY_TYPE);
+ task.setTaskToReturnTo(fromHome
+ ? lastStack.topTask() == null
+ ? HOME_ACTIVITY_TYPE
+ : lastStack.topTask().taskType
+ : APPLICATION_ACTIVITY_TYPE);
}
} else {
task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
@@ -1851,17 +1847,17 @@
mTaskHistory.remove(task);
// Now put task at top.
- int stackNdx = mTaskHistory.size();
+ int taskNdx = mTaskHistory.size();
if (!isCurrentProfileLocked(task.userId)) {
// Put non-current user tasks below current user tasks.
- while (--stackNdx >= 0) {
- if (!isCurrentProfileLocked(mTaskHistory.get(stackNdx).userId)) {
+ while (--taskNdx >= 0) {
+ if (!isCurrentProfileLocked(mTaskHistory.get(taskNdx).userId)) {
break;
}
}
- ++stackNdx;
+ ++taskNdx;
}
- mTaskHistory.add(stackNdx, task);
+ mTaskHistory.add(taskNdx, task);
updateTaskMovement(task, true);
}
@@ -1869,7 +1865,8 @@
boolean doResume, boolean keepCurTransition, Bundle options) {
TaskRecord rTask = r.task;
final int taskId = rTask.taskId;
- if (taskForIdLocked(taskId) == null || newTask) {
+ // mLaunchTaskBehind tasks get placed at the back of the task stack.
+ if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
// Last activity in task had been removed or ActivityManagerService is reusing task.
// Insert or replace.
// Might not even be in.
@@ -1894,7 +1891,8 @@
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
- r.userId, r.info.configChanges, task.voiceSession != null);
+ r.userId, r.info.configChanges, task.voiceSession != null,
+ r.mLaunchTaskBehind);
if (VALIDATE_TOKENS) {
validateAppTokensLocked();
}
@@ -1948,14 +1946,16 @@
mNoAnimActivities.add(r);
} else {
mWindowManager.prepareAppTransition(newTask
- ? AppTransition.TRANSIT_TASK_OPEN
+ ? r.mLaunchTaskBehind
+ ? AppTransition.TRANSIT_TASK_OPEN_BEHIND
+ : AppTransition.TRANSIT_TASK_OPEN
: AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
mNoAnimActivities.remove(r);
}
mWindowManager.addAppToken(task.mActivities.indexOf(r),
r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
- r.info.configChanges, task.voiceSession != null);
+ r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
boolean doShow = true;
if (newTask) {
// Even though this activity is starting fresh, we still need
@@ -1971,7 +1971,12 @@
== ActivityOptions.ANIM_SCENE_TRANSITION) {
doShow = false;
}
- if (SHOW_APP_STARTING_PREVIEW && doShow) {
+ if (r.mLaunchTaskBehind) {
+ // Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
+ // tell WindowManager that r is visible even though it is at the back of the stack.
+ mWindowManager.setAppVisibility(r.appToken, true);
+ ensureActivitiesVisibleLocked(null, 0);
+ } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
// Figure out if we are transitioning from another activity that is
// "has the same starting icon" as the next one. This allows the
// window manager to keep the previous window it had previously
@@ -2002,7 +2007,7 @@
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
- r.info.configChanges, task.voiceSession != null);
+ r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
ActivityOptions.abort(options);
options = null;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index bc184c6..7c8dd81 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -144,6 +144,7 @@
static final int LOCK_TASK_END_MSG = FIRST_SUPERVISOR_STACK_MSG + 10;
static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11;
static final int CONTAINER_TASK_LIST_EMPTY_TIMEOUT = FIRST_SUPERVISOR_STACK_MSG + 12;
+ static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 13;
private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
@@ -1557,9 +1558,9 @@
break;
}
}
- final int launchBehindFlags = Intent.FLAG_ACTIVITY_LAUNCH_BEHIND |
- Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
- final boolean affiliateTask = (launchFlags & launchBehindFlags) == launchBehindFlags;
+
+ final boolean launchTaskBehind = r.mLaunchTaskBehind &&
+ (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
// For whatever reason this activity is being launched into a new
@@ -1709,7 +1710,7 @@
sourceStack.topActivity().task == sourceRecord.task)) {
// We really do want to push this one into the
// user's face, right now.
- if (affiliateTask && sourceRecord != null) {
+ if (launchTaskBehind && sourceRecord != null) {
intentActivity.setTaskToAffiliateWith(sourceRecord.task);
}
movedHome = true;
@@ -1886,7 +1887,7 @@
boolean newTask = false;
boolean keepCurTransition = false;
- TaskRecord taskToAffiliate = affiliateTask && sourceRecord != null ?
+ TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ?
sourceRecord.task : null;
// Should this be considered a new task?
@@ -1898,12 +1899,15 @@
}
newTask = true;
targetStack = adjustStackFocus(r, newTask);
- targetStack.moveToFront();
+ if (!launchTaskBehind) {
+ targetStack.moveToFront();
+ }
if (reuseTask == null) {
r.setTask(targetStack.createTaskRecord(getNextTaskId(),
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
- voiceSession, voiceInteractor, true), taskToAffiliate);
+ voiceSession, voiceInteractor, !launchTaskBehind /* toTop */),
+ taskToAffiliate);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
} else {
@@ -1997,7 +2001,10 @@
ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
targetStack.mLastPausedActivity = null;
targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);
- mService.setFocusedActivityLocked(r);
+ if (!launchTaskBehind) {
+ // Don't set focus on an activity that's going to the back.
+ mService.setFocusedActivityLocked(r);
+ }
return ActivityManager.START_SUCCESS;
}
@@ -2394,7 +2401,8 @@
mWindowManager.addAppToken(0, r.appToken, taskId, stackId,
r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
- r.userId, r.info.configChanges, task.voiceSession != null);
+ r.userId, r.info.configChanges, task.voiceSession != null,
+ r.mLaunchTaskBehind);
}
mWindowManager.addTask(taskId, stackId, false);
}
@@ -2642,6 +2650,19 @@
return true;
}
+ // Called when WindowManager has finished animating the launchingBehind activity to the back.
+ void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
+ r.mLaunchTaskBehind = false;
+ final TaskRecord task = r.task;
+ task.setLastThumbnail(task.stack.screenshotActivities(r));
+ mService.addRecentTaskLocked(task);
+ mWindowManager.setAppVisibility(r.appToken, false);
+ }
+
+ void scheduleLaunchTaskBehindComplete(IBinder token) {
+ mHandler.obtainMessage(LAUNCH_TASK_BEHIND_COMPLETE, token).sendToTarget();
+ }
+
void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
// First the front stacks. In case any are not fullscreen and are in front of home.
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
@@ -3293,6 +3314,14 @@
((ActivityContainer) msg.obj).onTaskListEmptyLocked();
}
} break;
+ case LAUNCH_TASK_BEHIND_COMPLETE: {
+ synchronized (mService) {
+ ActivityRecord r = ActivityRecord.forToken((IBinder) msg.obj);
+ if (r != null) {
+ handleLaunchTaskBehindCompleteLocked(r);
+ }
+ }
+ } break;
}
}
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index da444f9..ac19bde 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -186,6 +186,34 @@
}
}
+ public void noteSyncStart(String name, int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteSyncStartLocked(name, uid);
+ }
+ }
+
+ public void noteSyncFinish(String name, int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteSyncFinishLocked(name, uid);
+ }
+ }
+
+ public void noteJobStart(String name, int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteJobStartLocked(name, uid);
+ }
+ }
+
+ public void noteJobFinish(String name, int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteJobFinishLocked(name, uid);
+ }
+ }
+
public void noteStartWakelock(int uid, int pid, String name, String historyName, int type,
boolean unimportantForLogging) {
enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 2f25bd4..a20be73 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -83,7 +83,6 @@
int pssProcState = -1; // The proc state we are currently requesting pss for
boolean serviceb; // Process currently is on the service B list
boolean serviceHighRam; // We are forcing to service B list due to its RAM use
- boolean keeping; // Actively running code so don't kill due to that?
boolean setIsForeground; // Running foreground UI when last set?
boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
boolean hasClientActivities; // Are there any client services with activities?
@@ -225,8 +224,7 @@
pw.print(" lruSeq="); pw.print(lruSeq);
pw.print(" lastPss="); pw.print(lastPss);
pw.print(" lastCachedPss="); pw.println(lastCachedPss);
- pw.print(prefix); pw.print("keeping="); pw.print(keeping);
- pw.print(" cached="); pw.print(cached);
+ pw.print(prefix); pw.print("cached="); pw.print(cached);
pw.print(" empty="); pw.println(empty);
if (serviceb) {
pw.print(prefix); pw.print("serviceb="); pw.print(serviceb);
@@ -275,16 +273,15 @@
if (hasStartedServices) {
pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
}
- if (!keeping) {
+ if (setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
long wtime;
synchronized (mBatteryStats) {
wtime = mBatteryStats.getProcessWakeTime(info.uid,
pid, SystemClock.elapsedRealtime());
}
- long timeUsed = wtime - lastWakeTime;
pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
pw.print(" timeUsed=");
- TimeUtils.formatDuration(timeUsed, pw); pw.println("");
+ TimeUtils.formatDuration(wtime-lastWakeTime, pw); pw.println("");
pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime);
pw.print(" timeUsed=");
TimeUtils.formatDuration(curCpuTime-lastCpuTime, pw); pw.println("");
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 6fb8570..545723a 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -148,8 +148,8 @@
/**
* Request ConnectivityService display provisioning notification.
* arg1 = Whether to make the notification visible.
- * obj = Intent to be launched when notification selected by user.
- * replyTo = NetworkAgentInfo.messenger so ConnectivityService can identify sender.
+ * arg2 = NetID.
+ * obj = Intent to be launched when notification selected by user, null if !arg1.
*/
public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12;
@@ -447,9 +447,9 @@
// Initiate notification to sign-in.
Intent intent = new Intent(ACTION_SIGN_IN_REQUESTED);
intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetworkAgentInfo.network.netId));
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, 0,
+ Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1,
+ mNetworkAgentInfo.network.netId,
PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
- message.replyTo = mNetworkAgentInfo.messenger;
mConnectivityServiceHandler.sendMessage(message);
}
@@ -470,8 +470,8 @@
@Override
public void exit() {
- Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, 0, null);
- message.replyTo = mNetworkAgentInfo.messenger;
+ Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0,
+ mNetworkAgentInfo.network.netId, null);
mConnectivityServiceHandler.sendMessage(message);
mContext.unregisterReceiver(mUserRespondedBroadcastReceiver);
mUserRespondedBroadcastReceiver = null;
@@ -648,6 +648,7 @@
new InputStreamReader(socket.getInputStream()));
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("GET " + url.getFile() + " HTTP/1.1\r\nHost: " + url.getHost() +
+ "\r\nUser-Agent: " + System.getProperty("http.agent") +
"\r\nConnection: close\r\n\r\n");
writer.flush();
String response = reader.readLine();
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 1b40cdf..08d6fc9 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1212,8 +1212,7 @@
} else {
try {
mEventName = mSyncOperation.wakeLockName();
- mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_SYNC_START,
- mEventName, mSyncAdapterUid);
+ mBatteryStats.noteSyncStart(mEventName, mSyncAdapterUid);
} catch (RemoteException e) {
}
}
@@ -1232,8 +1231,7 @@
mBound = false;
mContext.unbindService(this);
try {
- mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_SYNC_FINISH,
- mEventName, mSyncAdapterUid);
+ mBatteryStats.noteSyncFinish(mEventName, mSyncAdapterUid);
} catch (RemoteException e) {
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 44b0f28..f75bdab 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -89,9 +89,15 @@
// If true, TV going to standby mode puts other devices also to standby.
private boolean mAutoDeviceOff;
+ // If true, TV wakes itself up when receiving <Text/Image View On>.
+ private boolean mAutoWakeup;
+
HdmiCecLocalDeviceTv(HdmiControlService service) {
super(service, HdmiCecDeviceInfo.DEVICE_TV);
mPrevPortId = Constants.INVALID_PORT_ID;
+ mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
+ true);
+ mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
}
@Override
@@ -121,8 +127,7 @@
@ServiceThreadOnly
protected void setPreferredAddress(int addr) {
assertRunOnServiceThread();
- SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
- String.valueOf(addr));
+ SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV, String.valueOf(addr));
}
private void registerAudioPortUpdateListener() {
@@ -491,7 +496,7 @@
@ServiceThreadOnly
protected boolean handleTextViewOn(HdmiCecMessage message) {
assertRunOnServiceThread();
- if (mService.isPowerStandbyOrTransient()) {
+ if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
mService.wakeUp();
}
// TODO: Connect to Hardware input manager to invoke TV App with the appropriate channel
@@ -1115,6 +1120,14 @@
void setAutoDeviceOff(boolean enabled) {
assertRunOnServiceThread();
mAutoDeviceOff = enabled;
+ mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, enabled);
+ }
+
+ @ServiceThreadOnly
+ void setAutoWakeup(boolean enabled) {
+ assertRunOnServiceThread();
+ mAutoWakeup = enabled;
+ mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, enabled);
}
@Override
@@ -1176,7 +1189,7 @@
if (!mService.isControlEnabled()) {
return;
}
- if (!initiatedByCec) {
+ if (!initiatedByCec && mAutoDeviceOff) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
mAddress, Constants.ADDR_BROADCAST));
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a9a391b..7db5f0c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -44,7 +44,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.Settings.Global;
-import android.provider.Settings.SettingNotFoundException;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 7f8b232..caae9f5 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -35,16 +35,20 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.app.IBatteryStats;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.IdleController;
@@ -52,8 +56,6 @@
import com.android.server.job.controllers.StateController;
import com.android.server.job.controllers.TimeController;
-import java.util.LinkedList;
-
/**
* Responsible for taking jobs representing work to be performed by a client app, and determining
* based on the criteria specified when that job should be run against the client application's
@@ -74,7 +76,7 @@
private static final int MAX_JOB_CONTEXTS_COUNT = 3;
static final String TAG = "JobManagerService";
/** Master list of jobs. */
- private final JobStore mJobs;
+ final JobStore mJobs;
static final int MSG_JOB_EXPIRED = 0;
static final int MSG_CHECK_JOB = 1;
@@ -84,33 +86,41 @@
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
* early.
*/
- private static final int MIN_IDLE_COUNT = 1;
+ static final int MIN_IDLE_COUNT = 1;
/**
* Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
* things early.
*/
- private static final int MIN_CONNECTIVITY_COUNT = 2;
+ static final int MIN_CONNECTIVITY_COUNT = 2;
/**
* Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
* some work early.
*/
- private static final int MIN_READY_JOBS_COUNT = 4;
+ static final int MIN_READY_JOBS_COUNT = 4;
/**
* Track Services that have currently active or pending jobs. The index is provided by
* {@link JobStatus#getServiceToken()}
*/
- private final List<JobServiceContext> mActiveServices = new LinkedList<JobServiceContext>();
+ final List<JobServiceContext> mActiveServices = new ArrayList<JobServiceContext>();
/** List of controllers that will notify this service of updates to jobs. */
- private List<StateController> mControllers;
+ List<StateController> mControllers;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
*/
- private final LinkedList<JobStatus> mPendingJobs = new LinkedList<JobStatus>();
+ final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>();
- private final JobHandler mHandler;
- private final JobSchedulerStub mJobSchedulerStub;
+ final JobHandler mHandler;
+ final JobSchedulerStub mJobSchedulerStub;
+
+ IBatteryStats mBatteryStats;
+
+ /**
+ * Set to true once we are allowed to run third party apps.
+ */
+ boolean mReadyToRock;
+
/**
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
* still clean up. On reinstall the package will have a new uid.
@@ -152,7 +162,9 @@
public List<JobInfo> getPendingJobs(int uid) {
ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
synchronized (mJobs) {
- for (JobStatus job : mJobs.getJobs()) {
+ ArraySet<JobStatus> jobs = mJobs.getJobs();
+ for (int i=0; i<jobs.size(); i++) {
+ JobStatus job = jobs.valueAt(i);
if (job.getUid() == uid) {
outList.add(job.getJob());
}
@@ -164,7 +176,8 @@
private void cancelJobsForUser(int userHandle) {
synchronized (mJobs) {
List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
- for (JobStatus toRemove : jobsForUser) {
+ for (int i=0; i<jobsForUser.size(); i++) {
+ JobStatus toRemove = jobsForUser.get(i);
if (DEBUG) {
Slog.d(TAG, "Cancelling: " + toRemove);
}
@@ -183,7 +196,8 @@
// Remove from master list.
synchronized (mJobs) {
List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
- for (JobStatus toRemove : jobsForUid) {
+ for (int i=0; i<jobsForUid.size(); i++) {
+ JobStatus toRemove = jobsForUid.get(i);
if (DEBUG) {
Slog.d(TAG, "Cancelling: " + toRemove);
}
@@ -230,7 +244,7 @@
public JobSchedulerService(Context context) {
super(context);
// Create the controllers.
- mControllers = new LinkedList<StateController>();
+ mControllers = new ArrayList<StateController>();
mControllers.add(ConnectivityController.get(this));
mControllers.add(TimeController.get(this));
mControllers.add(IdleController.get(this));
@@ -238,11 +252,6 @@
mHandler = new JobHandler(context.getMainLooper());
mJobSchedulerStub = new JobSchedulerStub();
- // Create the "runners".
- for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
- mActiveServices.add(
- new JobServiceContext(this, context.getMainLooper()));
- }
mJobs = JobStore.initAndGet(this);
}
@@ -262,6 +271,29 @@
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
+ } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ synchronized (mJobs) {
+ // Let's go!
+ mReadyToRock = true;
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+ // Create the "runners".
+ for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
+ mActiveServices.add(
+ new JobServiceContext(this, mBatteryStats,
+ getContext().getMainLooper()));
+ }
+ // Attach jobs to their controllers.
+ ArraySet<JobStatus> jobs = mJobs.getJobs();
+ for (int i=0; i<jobs.size(); i++) {
+ JobStatus job = jobs.valueAt(i);
+ for (int j=0; j<mControllers.size(); j++) {
+ mControllers.get(i).maybeStartTrackingJob(job);
+ }
+ }
+ // GO GO GO!
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
}
}
@@ -272,14 +304,19 @@
*/
private void startTrackingJob(JobStatus jobStatus) {
boolean update;
+ boolean rocking;
synchronized (mJobs) {
update = mJobs.add(jobStatus);
+ rocking = mReadyToRock;
}
- for (StateController controller : mControllers) {
- if (update) {
- controller.maybeStopTrackingJob(jobStatus);
+ if (rocking) {
+ for (int i=0; i<mControllers.size(); i++) {
+ StateController controller = mControllers.get(i);
+ if (update) {
+ controller.maybeStopTrackingJob(jobStatus);
+ }
+ controller.maybeStartTrackingJob(jobStatus);
}
- controller.maybeStartTrackingJob(jobStatus);
}
}
@@ -289,12 +326,15 @@
*/
private boolean stopTrackingJob(JobStatus jobStatus) {
boolean removed;
+ boolean rocking;
synchronized (mJobs) {
// Remove from store as well as controllers.
removed = mJobs.remove(jobStatus);
+ rocking = mReadyToRock;
}
- if (removed) {
- for (StateController controller : mControllers) {
+ if (removed && rocking) {
+ for (int i=0; i<mControllers.size(); i++) {
+ StateController controller = mControllers.get(i);
controller.maybeStopTrackingJob(jobStatus);
}
}
@@ -302,7 +342,8 @@
}
private boolean stopJobOnServiceContextLocked(JobStatus job) {
- for (JobServiceContext jsc : mActiveServices) {
+ for (int i=0; i<mActiveServices.size(); i++) {
+ JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJob();
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
jsc.cancelExecutingJob();
@@ -318,7 +359,8 @@
* is pending.
*/
private boolean isCurrentlyActiveLocked(JobStatus job) {
- for (JobServiceContext serviceContext : mActiveServices) {
+ for (int i=0; i<mActiveServices.size(); i++) {
+ JobServiceContext serviceContext = mActiveServices.get(i);
final JobStatus running = serviceContext.getRunningJob();
if (running != null && running.matches(job.getUid(), job.getJobId())) {
return true;
@@ -431,8 +473,13 @@
*/
@Override
public void onControllerStateChanged() {
- // Post a message to to run through the list of jobs and start/stop any that are eligible.
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ synchronized (mJobs) {
+ if (mReadyToRock) {
+ // Post a message to to run through the list of jobs and start/stop any that
+ // are eligible.
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
+ }
}
@Override
@@ -449,7 +496,8 @@
@Override
public void onJobMapReadFinished(List<JobStatus> jobs) {
synchronized (mJobs) {
- for (JobStatus js : jobs) {
+ for (int i=0; i<jobs.size(); i++) {
+ JobStatus js = jobs.get(i);
if (mJobs.containsJobIdForUid(js.getJobId(), js.getUid())) {
// An app with BOOT_COMPLETED *might* have decided to reschedule their job, in
// the same amount of time it took us to read it from disk. If this is the case
@@ -495,7 +543,9 @@
*/
private void queueReadyJobsForExecutionH() {
synchronized (mJobs) {
- for (JobStatus job : mJobs.getJobs()) {
+ ArraySet<JobStatus> jobs = mJobs.getJobs();
+ for (int i=0; i<jobs.size(); i++) {
+ JobStatus job = jobs.valueAt(i);
if (isReadyToBeExecutedLocked(job)) {
mPendingJobs.add(job);
} else if (isReadyToBeCancelledLocked(job)) {
@@ -520,7 +570,9 @@
int backoffCount = 0;
int connectivityCount = 0;
List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
- for (JobStatus job : mJobs.getJobs()) {
+ ArraySet<JobStatus> jobs = mJobs.getJobs();
+ for (int i=0; i<jobs.size(); i++) {
+ JobStatus job = jobs.valueAt(i);
if (isReadyToBeExecutedLocked(job)) {
if (job.getNumFailures() > 0) {
backoffCount++;
@@ -539,8 +591,8 @@
if (backoffCount > 0 || idleCount >= MIN_IDLE_COUNT ||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
- for (JobStatus job : runnableJobs) {
- mPendingJobs.add(job);
+ for (int i=0; i<runnableJobs.size(); i++) {
+ mPendingJobs.add(runnableJobs.get(i));
}
}
}
@@ -576,7 +628,8 @@
while (it.hasNext()) {
JobStatus nextPending = it.next();
JobServiceContext availableContext = null;
- for (JobServiceContext jsc : mActiveServices) {
+ for (int i=0; i<mActiveServices.size(); i++) {
+ JobServiceContext jsc = mActiveServices.get(i);
final JobStatus running = jsc.getRunningJob();
if (running != null && running.matches(nextPending.getUid(),
nextPending.getJobId())) {
@@ -737,25 +790,28 @@
synchronized (mJobs) {
pw.println("Registered jobs:");
if (mJobs.size() > 0) {
- for (JobStatus job : mJobs.getJobs()) {
+ ArraySet<JobStatus> jobs = mJobs.getJobs();
+ for (int i=0; i<jobs.size(); i++) {
+ JobStatus job = jobs.valueAt(i);
job.dump(pw, " ");
}
} else {
pw.println();
pw.println("No jobs scheduled.");
}
- for (StateController controller : mControllers) {
+ for (int i=0; i<mControllers.size(); i++) {
pw.println();
- controller.dumpControllerState(pw);
+ mControllers.get(i).dumpControllerState(pw);
}
pw.println();
pw.println("Pending");
- for (JobStatus jobStatus : mPendingJobs) {
- pw.println(jobStatus.hashCode());
+ for (int i=0; i<mPendingJobs.size(); i++) {
+ pw.println(mPendingJobs.get(i).hashCode());
}
pw.println();
pw.println("Active jobs:");
- for (JobServiceContext jsc : mActiveServices) {
+ for (int i=0; i<mActiveServices.size(); i++) {
+ JobServiceContext jsc = mActiveServices.get(i);
if (jsc.isAvailable()) {
continue;
} else {
@@ -765,6 +821,8 @@
"timeout: " + jsc.getTimeoutElapsed());
}
}
+ pw.println();
+ pw.print("mReadyToRock="); pw.println(mReadyToRock);
}
pw.println();
}
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 534faba3..eaf5480 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -39,6 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
import com.android.server.job.controllers.JobStatus;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,8 +58,6 @@
private static final long EXECUTING_TIMESLICE_MILLIS = 60 * 1000;
/** Amount of time the JobScheduler will wait for a response from an app for a message. */
private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
- /** String prefix for all wakelock names. */
- private static final String JS_WAKELOCK_PREFIX = "*job*/";
private static final String[] VERB_STRINGS = {
"VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_PENDING"
@@ -87,6 +86,7 @@
private final JobCompletedListener mCompletedListener;
/** Used for service binding, etc. */
private final Context mContext;
+ private final IBatteryStats mBatteryStats;
private PowerManager.WakeLock mWakeLock;
// Execution state.
@@ -109,13 +109,15 @@
/** Track when job will timeout. */
private long mTimeoutElapsed;
- JobServiceContext(JobSchedulerService service, Looper looper) {
- this(service.getContext(), service, looper);
+ JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, Looper looper) {
+ this(service.getContext(), batteryStats, service, looper);
}
@VisibleForTesting
- JobServiceContext(Context context, JobCompletedListener completedListener, Looper looper) {
+ JobServiceContext(Context context, IBatteryStats batteryStats,
+ JobCompletedListener completedListener, Looper looper) {
mContext = context;
+ mBatteryStats = batteryStats;
mCallbackHandler = new JobServiceHandler(looper);
mCompletedListener = completedListener;
mAvailable = true;
@@ -152,6 +154,11 @@
mExecutionStartTimeElapsed = 0L;
return false;
}
+ try {
+ mBatteryStats.noteJobStart(job.getName(), job.getUid());
+ } catch (RemoteException e) {
+ // Whatever.
+ }
mAvailable = false;
return true;
}
@@ -228,8 +235,7 @@
mCallbackHandler.removeMessages(MSG_TIMEOUT);
final PowerManager pm =
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- JS_WAKELOCK_PREFIX + mRunningJob.getServiceComponent().getPackageName());
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mRunningJob.getTag());
mWakeLock.setWorkSource(new WorkSource(mRunningJob.getUid()));
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
@@ -483,6 +489,11 @@
removeMessages(MSG_TIMEOUT);
mCompletedListener.onJobCompleted(mRunningJob, reschedule);
synchronized (mLock) {
+ try {
+ mBatteryStats.noteJobFinish(mRunningJob.getName(), mRunningJob.getUid());
+ } catch (RemoteException e) {
+ // Whatever.
+ }
mWakeLock.release();
mContext.unbindService(JobServiceContext.this);
mWakeLock = null;
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 8736980..48312b0 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -136,7 +136,8 @@
* Whether this jobStatus object already exists in the JobStore.
*/
public boolean containsJobIdForUid(int jobId, int uId) {
- for (JobStatus ts : mJobSet) {
+ for (int i=mJobSet.size()-1; i>=0; i--) {
+ JobStatus ts = mJobSet.valueAt(i);
if (ts.getUid() == uId && ts.getJobId() == jobId) {
return true;
}
@@ -267,7 +268,8 @@
List<JobStatus> mStoreCopy = new ArrayList<JobStatus>();
synchronized (JobStore.this) {
// Copy over the jobs so we can release the lock before writing.
- for (JobStatus jobStatus : mJobSet) {
+ for (int i=0; i<mJobSet.size(); i++) {
+ JobStatus jobStatus = mJobSet.valueAt(i);
JobStatus copy = new JobStatus(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed());
mStoreCopy.add(copy);
@@ -290,7 +292,8 @@
out.startTag(null, "job-info");
out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
- for (JobStatus jobStatus : jobList) {
+ for (int i=0; i<jobList.size(); i++) {
+ JobStatus jobStatus = jobList.get(i);
if (DEBUG) {
Slog.d(TAG, "Saving job " + jobStatus.getJobId());
}
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 9ee2869..652d8f8 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -42,6 +42,8 @@
final JobInfo job;
final int uId;
+ final String name;
+ final String tag;
// Constraints.
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
@@ -72,6 +74,8 @@
private JobStatus(JobInfo job, int uId, int numFailures) {
this.job = job;
this.uId = uId;
+ this.name = job.getService().flattenToShortString();
+ this.tag = "*job*/" + this.name;
this.numFailures = numFailures;
}
@@ -140,6 +144,14 @@
return uId;
}
+ public String getName() {
+ return name;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
public PersistableBundle getExtras() {
return job.getExtras();
}
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index 8222155..b3419c1 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -62,6 +62,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
@@ -70,6 +71,7 @@
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
+import android.text.TextUtils;
import android.util.Log;
import android.util.NtpTrustedTime;
@@ -85,6 +87,8 @@
import java.util.Map.Entry;
import java.util.Properties;
+import libcore.io.IoUtils;
+
/**
* A GPS implementation of LocationProvider used by LocationManager.
*
@@ -201,7 +205,9 @@
private static final int AGPS_SETID_TYPE_IMSI = 1;
private static final int AGPS_SETID_TYPE_MSISDN = 2;
- private static final String PROPERTIES_FILE = "/etc/gps.conf";
+ private static final String PROPERTIES_FILE_PREFIX = "/etc/gps";
+ private static final String PROPERTIES_FILE_SUFFIX = ".conf";
+ private static final String DEFAULT_PROPERTIES_FILE = PROPERTIES_FILE_PREFIX + PROPERTIES_FILE_SUFFIX;
private static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L;
private static final int GPS_GEOFENCE_AVAILABLE = 1<<1L;
@@ -441,6 +447,44 @@
return native_is_supported();
}
+ private boolean loadPropertiesFile(String filename) {
+ mProperties = new Properties();
+ try {
+ File file = new File(filename);
+ FileInputStream stream = null;
+ try {
+ stream = new FileInputStream(file);
+ mProperties.load(stream);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+
+ mSuplServerHost = mProperties.getProperty("SUPL_HOST");
+ String portString = mProperties.getProperty("SUPL_PORT");
+ if (mSuplServerHost != null && portString != null) {
+ try {
+ mSuplServerPort = Integer.parseInt(portString);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
+ }
+ }
+
+ mC2KServerHost = mProperties.getProperty("C2K_HOST");
+ portString = mProperties.getProperty("C2K_PORT");
+ if (mC2KServerHost != null && portString != null) {
+ try {
+ mC2KServerPort = Integer.parseInt(portString);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "unable to parse C2K_PORT: " + portString);
+ }
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Could not open GPS configuration file " + filename);
+ return false;
+ }
+ return true;
+ }
+
public GpsLocationProvider(Context context, ILocationManager ilocationManager,
Looper looper) {
mContext = context;
@@ -469,34 +513,15 @@
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
- mProperties = new Properties();
- try {
- File file = new File(PROPERTIES_FILE);
- FileInputStream stream = new FileInputStream(file);
- mProperties.load(stream);
- stream.close();
+ boolean propertiesLoaded = false;
+ final String gpsHardware = SystemProperties.get("ro.hardware.gps");
+ if (!TextUtils.isEmpty(gpsHardware)) {
+ final String propFilename = PROPERTIES_FILE_PREFIX + "." + gpsHardware + PROPERTIES_FILE_SUFFIX;
+ propertiesLoaded = loadPropertiesFile(propFilename);
+ }
- mSuplServerHost = mProperties.getProperty("SUPL_HOST");
- String portString = mProperties.getProperty("SUPL_PORT");
- if (mSuplServerHost != null && portString != null) {
- try {
- mSuplServerPort = Integer.parseInt(portString);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
- }
- }
-
- mC2KServerHost = mProperties.getProperty("C2K_HOST");
- portString = mProperties.getProperty("C2K_PORT");
- if (mC2KServerHost != null && portString != null) {
- try {
- mC2KServerPort = Integer.parseInt(portString);
- } catch (NumberFormatException e) {
- Log.e(TAG, "unable to parse C2K_PORT: " + portString);
- }
- }
- } catch (IOException e) {
- Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
+ if (!propertiesLoaded) {
+ loadPropertiesFile(DEFAULT_PROPERTIES_FILE);
}
// construct handler, listen for events
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 341c7a9..01a21f4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -505,16 +505,19 @@
|| state.getState() == PlaybackState.STATE_FAST_FORWARDING
|| state.getState() == PlaybackState.STATE_REWINDING) {
long updateTime = state.getLastPositionUpdateTime();
+ long currentTime = SystemClock.elapsedRealtime();
if (updateTime > 0) {
- long position = (long) (state.getPlaybackRate()
- * (SystemClock.elapsedRealtime() - updateTime)) + state.getPosition();
+ long position = (long) (state.getPlaybackSpeed()
+ * (currentTime - updateTime)) + state.getPosition();
if (duration >= 0 && position > duration) {
position = duration;
} else if (position < 0) {
position = 0;
}
- result = new PlaybackState(state);
- result.setState(state.getState(), position, state.getPlaybackRate());
+ PlaybackState.Builder builder = new PlaybackState.Builder(state);
+ builder.setState(state.getState(), position, state.getPlaybackSpeed(),
+ currentTime);
+ result = builder.build();
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 6cd4019..0546a55 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -28,6 +28,12 @@
if (lhs.isRecentlyIntrusive() != rhs.isRecentlyIntrusive()) {
return lhs.isRecentlyIntrusive() ? -1 : 1;
}
+ final int leftPackagePriority = lhs.getPackagePriority();
+ final int rightPackagePriority = rhs.getPackagePriority();
+ if (leftPackagePriority != rightPackagePriority) {
+ // by priority, high to low
+ return -1 * Integer.compare(leftPackagePriority, rightPackagePriority);
+ }
final int leftScore = lhs.sbn.getScore();
final int rightScore = rhs.sbn.getScore();
if (leftScore != rightScore) {
diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
index d8ab9d7..1335706 100644
--- a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
@@ -63,4 +63,9 @@
}
};
}
-}
\ No newline at end of file
+
+ @Override
+ public void setConfig(RankingConfig config) {
+ // ignore: config has no relevant information yet.
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 892f61f..bbe0885 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -103,11 +103,9 @@
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
-import java.util.concurrent.TimeUnit;
/** {@hide} */
public class NotificationManagerService extends SystemService {
@@ -120,7 +118,8 @@
static final int MESSAGE_TIMEOUT = 2;
static final int MESSAGE_SAVE_POLICY_FILE = 3;
static final int MESSAGE_RECONSIDER_RANKING = 4;
- static final int MESSAGE_SEND_RANKING_UPDATE = 5;
+ static final int MESSAGE_RANKING_CONFIG_CHANGE = 5;
+ static final int MESSAGE_SEND_RANKING_UPDATE = 6;
static final int LONG_DELAY = 3500; // 3.5 seconds
static final int SHORT_DELAY = 2000; // 2 seconds
@@ -152,7 +151,6 @@
private WorkerHandler mHandler;
private final HandlerThread mRankingThread = new HandlerThread("ranker",
Process.THREAD_PRIORITY_BACKGROUND);
- private Handler mRankingHandler = null;
private Light mNotificationLight;
Light mAttentionLight;
@@ -178,7 +176,6 @@
// used as a mutex for access to all active notifications & listeners
final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
- final NotificationComparator mRankingComparator = new NotificationComparator();
final ArrayMap<String, NotificationRecord> mNotificationsByKey =
new ArrayMap<String, NotificationRecord>();
final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
@@ -203,7 +200,7 @@
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME = "name";
- final ArrayList<NotificationSignalExtractor> mSignalExtractors = new ArrayList<NotificationSignalExtractor>();
+ private RankingHelper mRankingHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -360,6 +357,7 @@
}
}
mZenModeHelper.readXml(parser);
+ mRankingHelper.readXml(parser);
}
} catch (FileNotFoundException e) {
// No data yet
@@ -398,6 +396,7 @@
out.startTag(null, TAG_BODY);
out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
mZenModeHelper.writeXml(out);
+ mRankingHelper.writeXml(out);
out.endTag(null, TAG_BODY);
out.endDocument();
mPolicyFile.finishWrite(stream);
@@ -752,13 +751,23 @@
@Override
public void onStart() {
+ Resources resources = getContext().getResources();
+
mAm = ActivityManagerNative.getDefault();
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mHandler = new WorkerHandler();
mRankingThread.start();
- mRankingHandler = new RankingWorkerHandler(mRankingThread.getLooper());
+ String[] extractorNames;
+ try {
+ extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
+ } catch (Resources.NotFoundException e) {
+ extractorNames = new String[0];
+ }
+ mRankingHelper = new RankingHelper(getContext(),
+ new RankingWorkerHandler(mRankingThread.getLooper()),
+ extractorNames);
mZenModeHelper = new ZenModeHelper(getContext(), mHandler);
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
@@ -782,7 +791,6 @@
mNotificationLight = lights.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
mAttentionLight = lights.getLight(LightsManager.LIGHT_ID_ATTENTION);
- Resources resources = getContext().getResources();
mDefaultNotificationColor = resources.getColor(
R.color.config_defaultNotificationColor);
mDefaultNotificationLedOn = resources.getInteger(
@@ -837,25 +845,6 @@
mSettingsObserver = new SettingsObserver(mHandler);
- // spin up NotificationSignalExtractors
- String[] extractorNames = resources.getStringArray(
- R.array.config_notificationSignalExtractors);
- for (String extractorName : extractorNames) {
- try {
- Class<?> extractorClass = getContext().getClassLoader().loadClass(extractorName);
- NotificationSignalExtractor extractor =
- (NotificationSignalExtractor) extractorClass.newInstance();
- extractor.initialize(getContext());
- mSignalExtractors.add(extractor);
- } catch (ClassNotFoundException e) {
- Slog.w(TAG, "Couldn't find extractor " + extractorName + ".", e);
- } catch (InstantiationException e) {
- Slog.w(TAG, "Couldn't instantiate extractor " + extractorName + ".", e);
- } catch (IllegalAccessException e) {
- Slog.w(TAG, "Problem accessing extractor " + extractorName + ".", e);
- }
- }
-
mArchive = new Archive(resources.getInteger(
R.integer.config_notificationServiceArchiveSize));
@@ -1062,6 +1051,19 @@
== AppOpsManager.MODE_ALLOWED);
}
+ @Override
+ public void setPackagePriority(String pkg, int uid, int priority) {
+ checkCallerIsSystem();
+ mRankingHelper.setPackagePriority(pkg, uid, priority);
+ savePolicyFile();
+ }
+
+ @Override
+ public int getPackagePriority(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mRankingHelper.getPackagePriority(pkg, uid);
+ }
+
/**
* System-only API for getting a list of current (i.e. not cleared) notifications.
*
@@ -1402,6 +1404,9 @@
mZenModeHelper.dump(pw, " ");
}
+ pw.println("\n Ranking Config:");
+ mRankingHelper.dump(pw, " ", filter);
+
pw.println("\n Notification listeners:");
mListeners.dump(pw, filter);
@@ -1509,16 +1514,7 @@
// Retain ranking information from previous record
r.copyRankingInformation(old);
}
- if (!mSignalExtractors.isEmpty()) {
- for (NotificationSignalExtractor extractor : mSignalExtractors) {
- try {
- RankingReconsideration recon = extractor.process(r);
- scheduleRankingReconsideration(recon);
- } catch (Throwable t) {
- Slog.w(TAG, "NotificationSignalExtractor failed.", t);
- }
- }
- }
+ mRankingHelper.extractSignals(r);
// 3. Apply local rules
@@ -1563,7 +1559,7 @@
applyZenModeLocked(r);
- Collections.sort(mNotificationList, mRankingComparator);
+ mRankingHelper.sort(mNotificationList);
if (notification.icon != 0) {
mListeners.notifyPostedLocked(n);
@@ -1838,14 +1834,6 @@
}
}
- private void scheduleRankingReconsideration(RankingReconsideration recon) {
- if (recon != null) {
- Message m = Message.obtain(mRankingHandler, MESSAGE_RECONSIDER_RANKING, recon);
- long delay = recon.getDelay(TimeUnit.MILLISECONDS);
- mRankingHandler.sendMessageDelayed(m, delay);
- }
- }
-
private void handleRankingReconsideration(Message message) {
if (!(message.obj instanceof RankingReconsideration)) return;
RankingReconsideration recon = (RankingReconsideration) message.obj;
@@ -1860,7 +1848,7 @@
boolean interceptBefore = record.isIntercepted();
recon.applyChangesLocked(record);
applyZenModeLocked(record);
- Collections.sort(mNotificationList, mRankingComparator);
+ mRankingHelper.sort(mNotificationList);
int indexAfter = findNotificationRecordIndexLocked(record);
boolean interceptAfter = record.isIntercepted();
changed = indexBefore != indexAfter || interceptBefore != interceptAfter;
@@ -1873,6 +1861,25 @@
}
}
+ private void handleRankingConfigChange() {
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ ArrayList<String> orderBefore = new ArrayList<String>(N);
+ for (int i = 0; i < N; i++) {
+ final NotificationRecord r = mNotificationList.get(i);
+ orderBefore.add(r.getKey());
+ mRankingHelper.extractSignals(r);
+ }
+ mRankingHelper.sort(mNotificationList);
+ for (int i = 0; i < N; i++) {
+ if (!orderBefore.get(i).equals(mNotificationList.get(i).getKey())) {
+ scheduleSendRankingUpdate();
+ return;
+ }
+ }
+ }
+ }
+
// let zen mode evaluate this record
private void applyZenModeLocked(NotificationRecord record) {
record.setIntercepted(mZenModeHelper.shouldIntercept(record));
@@ -1880,7 +1887,7 @@
// lock on mNotificationList
private int findNotificationRecordIndexLocked(NotificationRecord target) {
- return Collections.binarySearch(mNotificationList, target, mRankingComparator);
+ return mRankingHelper.indexOf(mNotificationList, target);
}
private void scheduleSendRankingUpdate() {
@@ -1928,6 +1935,9 @@
case MESSAGE_RECONSIDER_RANKING:
handleRankingReconsideration(msg);
break;
+ case MESSAGE_RANKING_CONFIG_CHANGE:
+ handleRankingConfigChange();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0e6265c..088b813 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -58,6 +58,7 @@
// Is this record an update of an old record?
public boolean isUpdate;
+ private int mPackagePriority;
NotificationRecord(StatusBarNotification sbn, int score)
{
@@ -70,6 +71,7 @@
public void copyRankingInformation(NotificationRecord previous) {
mContactAffinity = previous.mContactAffinity;
mRecentlyIntrusive = previous.mRecentlyIntrusive;
+ mPackagePriority = previous.mPackagePriority;
mIntercept = previous.mIntercept;
mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
}
@@ -141,6 +143,7 @@
pw.println(prefix + " stats=" + stats.toString());
pw.println(prefix + " mContactAffinity=" + mContactAffinity);
pw.println(prefix + " mRecentlyIntrusive=" + mRecentlyIntrusive);
+ pw.println(prefix + " mPackagePriority=" + mPackagePriority);
pw.println(prefix + " mIntercept=" + mIntercept);
pw.println(prefix + " mRankingTimeMs=" + mRankingTimeMs);
}
@@ -193,6 +196,14 @@
return mRecentlyIntrusive;
}
+ public void setPackagePriority(int packagePriority) {
+ mPackagePriority = packagePriority;
+ }
+
+ public int getPackagePriority() {
+ return mPackagePriority;
+ }
+
public boolean setIntercepted(boolean intercept) {
mIntercept = intercept;
return mIntercept;
@@ -230,5 +241,4 @@
}
return sbn.getPostTime();
}
-
}
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index 1537ea9..43d05d0 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -38,4 +38,10 @@
*/
public RankingReconsideration process(NotificationRecord notification);
+ /**
+ * Called whenever the {@link RankingConfig} changes.
+ *
+ * @param config information about which signals are important.
+ */
+ void setConfig(RankingConfig config);
}
diff --git a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java b/services/core/java/com/android/server/notification/PackagePriorityExtractor.java
new file mode 100644
index 0000000..9cdb3e1
--- /dev/null
+++ b/services/core/java/com/android/server/notification/PackagePriorityExtractor.java
@@ -0,0 +1,54 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.util.Slog;
+
+public class PackagePriorityExtractor implements NotificationSignalExtractor {
+ private static final String TAG = "ImportantPackageExtractor";
+ private static final boolean DBG = false;
+
+ private RankingConfig mConfig;
+
+ public void initialize(Context ctx) {
+ if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
+ }
+
+ public RankingReconsideration process(NotificationRecord record) {
+ if (record == null || record.getNotification() == null) {
+ if (DBG) Slog.d(TAG, "skipping empty notification");
+ return null;
+ }
+
+ if (mConfig == null) {
+ if (DBG) Slog.d(TAG, "missing config");
+ return null;
+ }
+
+ final int packagePriority = mConfig.getPackagePriority(
+ record.sbn.getPackageName(), record.sbn.getUid());
+ record.setPackagePriority(packagePriority);
+
+ return null;
+ }
+
+ @Override
+ public void setConfig(RankingConfig config) {
+ mConfig = config;
+ }
+}
diff --git a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl b/services/core/java/com/android/server/notification/RankingConfig.java
similarity index 67%
copy from telecomm/java/android/telecomm/CallServiceDescriptor.aidl
copy to services/core/java/com/android/server/notification/RankingConfig.java
index f517c73..7d0bd59 100644
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -1,5 +1,5 @@
-/*
- * Copyright 2014, The Android Open Source Project
+/**
+ * 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.
@@ -13,7 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.server.notification;
-package android.telecomm;
+public interface RankingConfig {
+ int getPackagePriority(String packageName, int uid);
-parcelable CallServiceDescriptor;
+ void setPackagePriority(String packageName, int uid, int priority);
+}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
new file mode 100644
index 0000000..fc03c17
--- /dev/null
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+public class RankingHelper implements RankingConfig {
+ private static final String TAG = "RankingHelper";
+ private static final boolean DEBUG = false;
+
+ private static final int XML_VERSION = 1;
+
+ private static final String TAG_RANKING = "ranking";
+ private static final String TAG_PACKAGE = "package";
+ private static final String ATT_VERSION = "version";
+
+ private static final String ATT_NAME = "name";
+ private static final String ATT_UID = "uid";
+ private static final String ATT_PRIORITY = "priority";
+
+ private static final String VALUE_HIGH = "high";
+
+ private final NotificationSignalExtractor[] mSignalExtractors;
+ private final NotificationComparator mRankingComparator = new NotificationComparator();
+
+ // Package name to uid, to priority. Would be better as Table<String, Int, Int>
+ private final ArrayMap<String, SparseIntArray> mPackagePriorities;
+
+ private final Context mContext;
+ private final Handler mRankingHandler;
+
+ public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) {
+ mContext = context;
+ mRankingHandler = rankingHandler;
+ mPackagePriorities = new ArrayMap<String, SparseIntArray>();
+
+ final int N = extractorNames.length;
+ mSignalExtractors = new NotificationSignalExtractor[N];
+ for (int i = 0; i < N; i++) {
+ try {
+ Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
+ NotificationSignalExtractor extractor =
+ (NotificationSignalExtractor) extractorClass.newInstance();
+ extractor.initialize(mContext);
+ extractor.setConfig(this);
+ mSignalExtractors[i] = extractor;
+ } catch (ClassNotFoundException e) {
+ Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
+ } catch (InstantiationException e) {
+ Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
+ } catch (IllegalAccessException e) {
+ Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
+ }
+ }
+ }
+
+ public void extractSignals(NotificationRecord r) {
+ final int N = mSignalExtractors.length;
+ for (int i = 0; i < N; i++) {
+ NotificationSignalExtractor extractor = mSignalExtractors[i];
+ try {
+ RankingReconsideration recon = extractor.process(r);
+ if (recon != null) {
+ Message m = Message.obtain(mRankingHandler,
+ NotificationManagerService.MESSAGE_RECONSIDER_RANKING, recon);
+ long delay = recon.getDelay(TimeUnit.MILLISECONDS);
+ mRankingHandler.sendMessageDelayed(m, delay);
+ }
+ } catch (Throwable t) {
+ Slog.w(TAG, "NotificationSignalExtractor failed.", t);
+ }
+ }
+ }
+
+ public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+ int type = parser.getEventType();
+ if (type != XmlPullParser.START_TAG) return;
+ String tag = parser.getName();
+ if (!TAG_RANKING.equals(tag)) return;
+ mPackagePriorities.clear();
+ final int version = safeInt(parser, ATT_VERSION, XML_VERSION);
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
+ return;
+ }
+ if (type == XmlPullParser.START_TAG) {
+ if (TAG_PACKAGE.equals(tag)) {
+ int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL);
+ int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT);
+ String name = parser.getAttributeValue(null, ATT_NAME);
+
+ if (!TextUtils.isEmpty(name) && priority != Notification.PRIORITY_DEFAULT) {
+ SparseIntArray priorityByUid = mPackagePriorities.get(name);
+ if (priorityByUid == null) {
+ priorityByUid = new SparseIntArray();
+ mPackagePriorities.put(name, priorityByUid);
+ }
+ priorityByUid.put(uid, priority);
+ }
+ }
+ }
+ }
+ throw new IllegalStateException("Failed to reach END_DOCUMENT");
+ }
+
+ public void writeXml(XmlSerializer out) throws IOException {
+ out.startTag(null, TAG_RANKING);
+ out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
+
+ final int N = mPackagePriorities.size();
+ for (int i = 0; i < N; i ++) {
+ String name = mPackagePriorities.keyAt(i);
+ SparseIntArray priorityByUid = mPackagePriorities.get(name);
+ final int M = priorityByUid.size();
+ for (int j = 0; j < M; j++) {
+ int uid = priorityByUid.keyAt(j);
+ int priority = priorityByUid.get(uid);
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATT_NAME, name);
+ out.attribute(null, ATT_UID, Integer.toString(uid));
+ out.attribute(null, ATT_PRIORITY, Integer.toString(priority));
+ out.endTag(null, TAG_PACKAGE);
+ }
+ }
+ out.endTag(null, TAG_RANKING);
+ }
+
+ private void updateConfig() {
+ final int N = mSignalExtractors.length;
+ for (int i = 0; i < N; i++) {
+ mSignalExtractors[i].setConfig(this);
+ }
+ mRankingHandler.sendEmptyMessage(NotificationManagerService.MESSAGE_RANKING_CONFIG_CHANGE);
+ }
+
+ public void sort(ArrayList<NotificationRecord> notificationList) {
+ Collections.sort(notificationList, mRankingComparator);
+ }
+
+ public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
+ return Collections.binarySearch(notificationList, target, mRankingComparator);
+ }
+
+ private static int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseInt(val, defValue);
+ }
+
+ private static int tryParseInt(String value, int defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ try {
+ return Integer.valueOf(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ @Override
+ public int getPackagePriority(String packageName, int uid) {
+ int priority = Notification.PRIORITY_DEFAULT;
+ SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
+ if (priorityByUid != null) {
+ priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT);
+ }
+ return priority;
+ }
+
+ @Override
+ public void setPackagePriority(String packageName, int uid, int priority) {
+ if (priority == getPackagePriority(packageName, uid)) {
+ return;
+ }
+ SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
+ if (priorityByUid == null) {
+ priorityByUid = new SparseIntArray();
+ mPackagePriorities.put(packageName, priorityByUid);
+ }
+ priorityByUid.put(uid, priority);
+ updateConfig();
+ }
+
+ public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
+ if (filter == null) {
+ final int N = mSignalExtractors.length;
+ pw.print(prefix);
+ pw.print("mSignalExtractors.length = ");
+ pw.println(N);
+ for (int i = 0; i < N; i++) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.println(mSignalExtractors[i]);
+ }
+ }
+ final int N = mPackagePriorities.size();
+ if (filter == null) {
+ pw.print(prefix);
+ pw.println("package priorities:");
+ }
+ for (int i = 0; i < N; i++) {
+ String name = mPackagePriorities.keyAt(i);
+ if (filter == null || filter.matches(name)) {
+ SparseIntArray priorityByUid = mPackagePriorities.get(name);
+ final int M = priorityByUid.size();
+ for (int j = 0; j < M; j++) {
+ int uid = priorityByUid.keyAt(j);
+ int priority = priorityByUid.get(uid);
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(name);
+ pw.print(" (");
+ pw.print(uid);
+ pw.print(") has priority: ");
+ pw.println(priority);
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 4ac2dcc..bdc364c 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -264,6 +264,11 @@
return validatePeople(record);
}
+ @Override
+ public void setConfig(RankingConfig config) {
+ // ignore: config has no relevant information yet.
+ }
+
private static class LookupResult {
private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000; // 1hr
public static final int INVALID_ID = -1;
diff --git a/services/core/java/com/android/server/pm/KeySetHandle.java b/services/core/java/com/android/server/pm/KeySetHandle.java
new file mode 100644
index 0000000..640feb3
--- /dev/null
+++ b/services/core/java/com/android/server/pm/KeySetHandle.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.os.Binder;
+
+public class KeySetHandle extends Binder {
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index c19951f..37bedf3 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import android.content.pm.KeySet;
import android.content.pm.PackageParser;
import android.os.Binder;
import android.util.ArraySet;
@@ -52,7 +51,7 @@
/** Sentinel value returned when public key is not found. */
protected static final long PUBLIC_KEY_NOT_FOUND = -1;
- private final LongSparseArray<KeySet> mKeySets;
+ private final LongSparseArray<KeySetHandle> mKeySets;
private final LongSparseArray<PublicKey> mPublicKeys;
@@ -65,7 +64,7 @@
private static long lastIssuedKeyId = 0;
public KeySetManagerService(Map<String, PackageSetting> packages) {
- mKeySets = new LongSparseArray<KeySet>();
+ mKeySets = new LongSparseArray<KeySetHandle>();
mPublicKeys = new LongSparseArray<PublicKey>();
mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
mPackages = packages;
@@ -82,7 +81,7 @@
*
* Note that this can return true for multiple KeySets.
*/
- public boolean packageIsSignedByLPr(String packageName, KeySet ks) {
+ public boolean packageIsSignedByLPr(String packageName, KeySetHandle ks) {
PackageSetting pkg = mPackages.get(packageName);
if (pkg == null) {
throw new NullPointerException("Invalid package name");
@@ -91,16 +90,42 @@
throw new NullPointerException("Package has no KeySet data");
}
long id = getIdByKeySetLPr(ks);
+ if (id == KEYSET_NOT_FOUND) {
+ return false;
+ }
return pkg.keySetData.packageIsSignedBy(id);
}
/**
+ * Determine if a package is signed by the given KeySet.
+ *
+ * Returns false if the package was not signed by all the
+ * keys in the KeySet, or if the package was signed by keys
+ * not in the KeySet.
+ *
+ * Note that this can return only for one KeySet.
+ */
+ public boolean packageIsSignedByExactlyLPr(String packageName, KeySetHandle ks) {
+ PackageSetting pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ throw new NullPointerException("Invalid package name");
+ }
+ if (pkg.keySetData == null
+ || pkg.keySetData.getProperSigningKeySet()
+ == PackageKeySetData.KEYSET_UNASSIGNED) {
+ throw new NullPointerException("Package has no KeySet data");
+ }
+ long id = getIdByKeySetLPr(ks);
+ return pkg.keySetData.getProperSigningKeySet() == id;
+ }
+
+ /**
* This informs the system that the given package has defined a KeySet
* in its manifest that a) contains the given keys and b) is named
* alias by that package.
*/
public void addDefinedKeySetToPackageLPw(String packageName,
- Set<PublicKey> keys, String alias) {
+ ArraySet<PublicKey> keys, String alias) {
if ((packageName == null) || (keys == null) || (alias == null)) {
Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
return;
@@ -110,7 +135,7 @@
throw new NullPointerException("Unknown package");
}
// Add to KeySets, then to package
- KeySet ks = addKeySetLPw(keys);
+ KeySetHandle ks = addKeySetLPw(keys);
long id = getIdByKeySetLPr(ks);
pkg.keySetData.addDefinedKeySet(id, alias);
}
@@ -137,19 +162,18 @@
* was signed by the provided KeySet.
*/
public void addSigningKeySetToPackageLPw(String packageName,
- Set<PublicKey> signingKeys) {
+ ArraySet<PublicKey> signingKeys) {
if ((packageName == null) || (signingKeys == null)) {
Slog.w(TAG, "Got null argument for a signing keyset, ignoring!");
return;
}
// add the signing KeySet
- KeySet ks = addKeySetLPw(signingKeys);
+ KeySetHandle ks = addKeySetLPw(signingKeys);
long id = getIdByKeySetLPr(ks);
- Set<Long> publicKeyIds = mKeySetMapping.get(id);
+ ArraySet<Long> publicKeyIds = mKeySetMapping.get(id);
if (publicKeyIds == null) {
throw new NullPointerException("Got invalid KeySet id");
}
-
// attach it to the package
PackageSetting pkg = mPackages.get(packageName);
if (pkg == null) {
@@ -160,7 +184,7 @@
// KeySet id to the package's signing KeySets
for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
long keySetID = mKeySets.keyAt(keySetIndex);
- Set<Long> definedKeys = mKeySetMapping.get(keySetID);
+ ArraySet<Long> definedKeys = mKeySetMapping.get(keySetID);
if (publicKeyIds.containsAll(definedKeys)) {
pkg.keySetData.addSigningKeySet(keySetID);
}
@@ -171,9 +195,9 @@
* Fetches the stable identifier associated with the given KeySet. Returns
* {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found.
*/
- private long getIdByKeySetLPr(KeySet ks) {
+ private long getIdByKeySetLPr(KeySetHandle ks) {
for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
- KeySet value = mKeySets.valueAt(keySetIndex);
+ KeySetHandle value = mKeySets.valueAt(keySetIndex);
if (ks.equals(value)) {
return mKeySets.keyAt(keySetIndex);
}
@@ -187,25 +211,24 @@
* Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't
* identify a {@link KeySet}.
*/
- public KeySet getKeySetByIdLPr(long id) {
+ public KeySetHandle getKeySetByIdLPr(long id) {
return mKeySets.get(id);
}
/**
- * Fetches the {@link KeySet} that a given package refers to by the provided alias.
- *
- * @throws IllegalArgumentException if the package has no keyset data.
- * @throws NullPointerException if the package is unknown.
+ * Fetches the {@link KeySetHandle} that a given package refers to by the
+ * provided alias. Returns null if the package is unknown or does not have a
+ * KeySet corresponding to that alias.
*/
- public KeySet getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
+ public KeySetHandle getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
PackageSetting p = mPackages.get(packageName);
- if (p == null) {
- throw new NullPointerException("Unknown package");
+ if (p == null || p.keySetData == null) {
+ return null;
}
- if (p.keySetData == null) {
- throw new IllegalArgumentException("Package has no keySet data");
+ Long keySetId = p.keySetData.getAliases().get(alias);
+ if (keySetId == null) {
+ throw new IllegalArgumentException("Unknown KeySet alias: " + alias);
}
- long keySetId = p.keySetData.getAliases().get(alias);
return mKeySets.get(keySetId);
}
@@ -214,7 +237,7 @@
* KeySet id.
*
* Returns {@code null} if the identifier doesn't
- * identify a {@link KeySet}.
+ * identify a {@link KeySetHandle}.
*/
public ArraySet<PublicKey> getPublicKeysFromKeySetLPr(long id) {
if(mKeySetMapping.get(id) == null) {
@@ -228,36 +251,32 @@
}
/**
- * Fetches all the known {@link KeySet KeySets} that signed the given
+ * Fetches the proper {@link KeySetHandle KeySet} that signed the given
* package.
*
* @throws IllegalArgumentException if the package has no keyset data.
* @throws NullPointerException if the package is unknown.
*/
- public Set<KeySet> getSigningKeySetsByPackageNameLPr(String packageName) {
- Set<KeySet> signingKeySets = new ArraySet<KeySet>();
+ public KeySetHandle getSigningKeySetByPackageNameLPr(String packageName) {
PackageSetting p = mPackages.get(packageName);
- if (p == null) {
- throw new NullPointerException("Unknown package");
+ if (p == null
+ || p.keySetData == null
+ || p.keySetData.getProperSigningKeySet()
+ == PackageKeySetData.KEYSET_UNASSIGNED) {
+ return null;
}
- if (p.keySetData == null || p.keySetData.getSigningKeySets() == null) {
- throw new IllegalArgumentException("Package has no keySet data");
- }
- for (long l : p.keySetData.getSigningKeySets()) {
- signingKeySets.add(mKeySets.get(l));
- }
- return signingKeySets;
+ return mKeySets.get(p.keySetData.getProperSigningKeySet());
}
/**
- * Fetches all the known {@link KeySet KeySets} that may upgrade the given
+ * Fetches all the known {@link KeySetHandle KeySets} that may upgrade the given
* package.
*
* @throws IllegalArgumentException if the package has no keyset data.
* @throws NullPointerException if the package is unknown.
*/
- public ArraySet<KeySet> getUpgradeKeySetsByPackageNameLPr(String packageName) {
- ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>();
+ public ArraySet<KeySetHandle> getUpgradeKeySetsByPackageNameLPr(String packageName) {
+ ArraySet<KeySetHandle> upgradeKeySets = new ArraySet<KeySetHandle>();
PackageSetting p = mPackages.get(packageName);
if (p == null) {
throw new NullPointerException("Unknown package");
@@ -287,7 +306,7 @@
*
* Throws if the provided set is {@code null}.
*/
- private KeySet addKeySetLPw(Set<PublicKey> keys) {
+ private KeySetHandle addKeySetLPw(ArraySet<PublicKey> keys) {
if (keys == null) {
throw new NullPointerException("Provided keys cannot be null");
}
@@ -305,7 +324,7 @@
}
// create the KeySet object
- KeySet ks = new KeySet(new Binder());
+ KeySetHandle ks = new KeySetHandle();
// get the first unoccupied slot in mKeySets
long id = getFreeKeySetIDLPw();
// add the KeySet object to it
@@ -318,7 +337,7 @@
if (p.keySetData != null) {
long pProperSigning = p.keySetData.getProperSigningKeySet();
if (pProperSigning != PackageKeySetData.KEYSET_UNASSIGNED) {
- Set<Long> pSigningKeys = mKeySetMapping.get(pProperSigning);
+ ArraySet<Long> pSigningKeys = mKeySetMapping.get(pProperSigning);
if (pSigningKeys.containsAll(addedKeyIds)) {
p.keySetData.addSigningKeySet(id);
}
@@ -353,7 +372,7 @@
*/
private long getIdFromKeyIdsLPr(Set<Long> publicKeyIds) {
for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) {
- Set<Long> value = mKeySetMapping.valueAt(keyMapIndex);
+ ArraySet<Long> value = mKeySetMapping.valueAt(keyMapIndex);
if (value.equals(publicKeyIds)) {
return mKeySetMapping.keyAt(keyMapIndex);
}
@@ -582,7 +601,7 @@
serializer.startTag(null, "keysets");
for (int keySetIndex = 0; keySetIndex < mKeySetMapping.size(); keySetIndex++) {
long id = mKeySetMapping.keyAt(keySetIndex);
- Set<Long> keys = mKeySetMapping.valueAt(keySetIndex);
+ ArraySet<Long> keys = mKeySetMapping.valueAt(keySetIndex);
serializer.startTag(null, "keyset");
serializer.attribute(null, "identifier", Long.toString(id));
for (long keyId : keys) {
@@ -662,7 +681,7 @@
final String tagName = parser.getName();
if (tagName.equals("keyset")) {
currentKeySetId = readIdentifierLPw(parser);
- mKeySets.put(currentKeySetId, new KeySet(new Binder()));
+ mKeySets.put(currentKeySetId, new KeySetHandle());
mKeySetMapping.put(currentKeySetId, new ArraySet<Long>());
} else if (tagName.equals("key-id")) {
long id = readIdentifierLPw(parser);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0eb922d..190e87c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -60,6 +60,7 @@
// TODO: destroy sessions with old timestamps
// TODO: remove outstanding sessions when installer package goes away
+ // TODO: notify listeners in other users when package has been installed there
private final Context mContext;
private final PackageManagerService mPm;
@@ -167,6 +168,10 @@
params.installFlags |= INSTALL_REPLACE_EXISTING;
}
+ if (params.mode == InstallSessionParams.MODE_INVALID) {
+ throw new IllegalArgumentException("Params must have valid mode set");
+ }
+
// Sanity check that install could fit
if (params.deltaSize > 0) {
try {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index c399fa2..31d9704 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -166,7 +166,7 @@
info.installerPackageName = installerPackageName;
info.progress = mProgress;
- info.fullInstall = params.fullInstall;
+ info.mode = params.mode;
info.packageName = params.packageName;
info.icon = params.icon;
info.title = params.title;
@@ -177,7 +177,8 @@
@Override
public void setClientProgress(int progress) {
mClientProgress = progress;
- mProgress = MathUtils.constrain((mClientProgress * 8 * 100) / (params.progressMax * 10), 0, 80);
+ mProgress = MathUtils.constrain(
+ (int) (((float) mClientProgress) / ((float) params.progressMax)) * 80, 0, 80);
mCallback.onSessionProgress(this, mProgress);
}
@@ -288,7 +289,7 @@
// Inherit any packages and native libraries from existing install that
// haven't been overridden.
- if (!params.fullInstall) {
+ if (params.mode == InstallSessionParams.MODE_INHERIT_EXISTING) {
spliceExistingFilesIntoStage();
}
@@ -382,7 +383,7 @@
// currently relying on PMS to do this.
// TODO: teach about compatible upgrade keysets.
- if (params.fullInstall) {
+ if (params.mode == InstallSessionParams.MODE_FULL_INSTALL) {
// Full installs must include a base package
if (!seenSplits.contains(null)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 101ef92..74a1945 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1713,7 +1713,7 @@
// NOTE: We ignore potential failures here during a system scan (like
// the rest of the commands above) because there's precious little we
// can do about it. A settings error is reported, though.
- adjustCpuAbisForSharedUserLPw(setting.packages, null,
+ adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
false /* force dexopt */, false /* defer dexopt */);
}
@@ -5428,11 +5428,8 @@
}
}
- if (abi32 < 0 && abi32 != PackageManager.NO_NATIVE_LIBRARIES) {
- throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
- "Error unpackaging 32 bit native libs for multiarch app, errorCode="
- + abi32);
- }
+ maybeThrowExceptionForMultiArchCopy(
+ "Error unpackaging 32 bit native libs for multiarch app.", abi32);
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
if (isAsec) {
@@ -5443,11 +5440,8 @@
}
}
- if (abi64 < 0 && abi64 != PackageManager.NO_NATIVE_LIBRARIES) {
- throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
- "Error unpackaging 64 bit native libs for multiarch app, errorCode="
- + abi32);
- }
+ maybeThrowExceptionForMultiArchCopy(
+ "Error unpackaging 64 bit native libs for multiarch app.", abi64);
if (abi64 >= 0) {
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
@@ -5544,11 +5538,8 @@
// We also do this *before* we perform dexopt on this package, so that
// we can avoid redundant dexopts, and also to make sure we've got the
// code and package path correct.
- if (!adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
- pkg, forceDex, (scanMode & SCAN_DEFER_DEX) != 0)) {
- throw new PackageManagerException(INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
- "scanPackageLI");
- }
+ adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
+ pkg, forceDex, (scanMode & SCAN_DEFER_DEX) != 0);
}
if ((scanMode&SCAN_NO_DEX) == 0) {
@@ -6047,7 +6038,7 @@
* NOTE: We currently only match for the primary CPU abi string. Matching the secondary
* adds unnecessary complexity.
*/
- private boolean adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
+ private void adjustCpuAbisForSharedUserLPw(Set<PackageSetting> packagesForUser,
PackageParser.Package scannedPackage, boolean forceDexOpt, boolean deferDexOpt) {
String requiredInstructionSet = null;
if (scannedPackage != null && scannedPackage.applicationInfo.primaryCpuAbi != null) {
@@ -6061,27 +6052,23 @@
// when scannedPackage is an update of an existing package. Without this check,
// we will never be able to change the ABI of any package belonging to a shared
// user, even if it's compatible with other packages.
- if (scannedPackage == null || ! scannedPackage.packageName.equals(ps.name)) {
+ if (scannedPackage == null || !scannedPackage.packageName.equals(ps.name)) {
if (ps.primaryCpuAbiString == null) {
continue;
}
final String instructionSet = VMRuntime.getInstructionSet(ps.primaryCpuAbiString);
- if (requiredInstructionSet != null) {
- if (!instructionSet.equals(requiredInstructionSet)) {
- // We have a mismatch between instruction sets (say arm vs arm64).
- // bail out.
- String errorMessage = "Instruction set mismatch, "
- + ((requirer == null) ? "[caller]" : requirer)
- + " requires " + requiredInstructionSet + " whereas " + ps
- + " requires " + instructionSet;
- Slog.e(TAG, errorMessage);
+ if (requiredInstructionSet != null && !instructionSet.equals(requiredInstructionSet)) {
+ // We have a mismatch between instruction sets (say arm vs arm64) warn about
+ // this but there's not much we can do.
+ String errorMessage = "Instruction set mismatch, "
+ + ((requirer == null) ? "[caller]" : requirer)
+ + " requires " + requiredInstructionSet + " whereas " + ps
+ + " requires " + instructionSet;
+ Slog.w(TAG, errorMessage);
+ }
- reportSettingsProblem(Log.WARN, errorMessage);
- // Give up, don't bother making any other changes to the package settings.
- return false;
- }
- } else {
+ if (requiredInstructionSet == null) {
requiredInstructionSet = instructionSet;
requirer = ps;
}
@@ -6118,7 +6105,7 @@
if (performDexOptLI(ps.pkg, forceDexOpt, deferDexOpt, true) == DEX_OPT_FAILED) {
ps.primaryCpuAbiString = null;
ps.pkg.applicationInfo.primaryCpuAbi = null;
- return false;
+ return;
} else {
mInstaller.rmdex(ps.codePathString, getPreferredInstructionSet());
}
@@ -6126,8 +6113,6 @@
}
}
}
-
- return true;
}
private void setUpCustomResolverActivity(PackageParser.Package pkg) {
@@ -6240,9 +6225,6 @@
if (info.primaryCpuAbi != null) {
info.nativeLibraryDir = new File(info.nativeLibraryRootDir,
VMRuntime.getInstructionSet(info.primaryCpuAbi)).getAbsolutePath();
- } else {
- Slog.w(TAG, "Package " + info.packageName
- + " missing ABI; unable to derive nativeLibraryDir");
}
} else {
info.nativeLibraryDir = info.nativeLibraryRootDir;
@@ -9264,29 +9246,13 @@
if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot,
Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */);
- if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
- Slog.w(TAG, "Failure copying 32 bit native libraries [errorCode=" + copyRet + "]");
- return copyRet;
- }
- }
-
- if (DEBUG_ABI_SELECTION && copyRet >= 0) {
- Log.d(TAG, "Installed 32 bit libraries under: " + codeFile + " abi=" +
- Build.SUPPORTED_32_BIT_ABIS[copyRet]);
+ maybeThrowExceptionForMultiArchCopy("Failure copying 32 bit native libraries", copyRet);
}
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
copyRet = copyNativeLibrariesForInternalApp(handle, libraryRoot,
Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */);
- if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
- Slog.w(TAG, "Failure copying 64 bit native libraries [errorCode=" + copyRet + "]");
- return copyRet;
- }
- }
-
- if (DEBUG_ABI_SELECTION && copyRet >= 0) {
- Log.d(TAG, "Installed 64 bit libraries under: " + codeFile + " abi=" +
- Build.SUPPORTED_64_BIT_ABIS[copyRet]);
+ maybeThrowExceptionForMultiArchCopy("Failure copying 64 bit native libraries", copyRet);
}
} else {
String[] abiList = (abiOverride != null) ?
@@ -9303,14 +9269,13 @@
Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]");
return copyRet;
}
-
- if (DEBUG_ABI_SELECTION && copyRet >= 0) {
- Log.d(TAG, "Installed libraries under: " + codeFile + " abi=" + abiList[copyRet]);
- }
}
} catch (IOException e) {
Slog.e(TAG, "Copying native libraries failed", e);
ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+ } catch (PackageManagerException pme) {
+ Slog.e(TAG, "Copying native libraries failed", pme);
+ ret = pme.error;
} finally {
IoUtils.closeQuietly(handle);
}
@@ -9331,14 +9296,18 @@
return false;
} else {
final File beforeCodeFile = codeFile;
- final File afterCodeFile = new File(mAppInstallDir,
- getNextCodePath(oldCodePath, pkg.packageName, null));
+ final File afterCodeFile = getNextCodePath(pkg.packageName);
Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
- if (!beforeCodeFile.renameTo(afterCodeFile)) {
+ try {
+ Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
+ } catch (ErrnoException e) {
+ Slog.d(TAG, "Failed to rename", e);
return false;
}
+
if (!SELinux.restoreconRecursive(afterCodeFile)) {
+ Slog.d(TAG, "Failed to restorecon");
return false;
}
@@ -9455,6 +9424,16 @@
return !asecPath.startsWith(mAsecInternalPath);
}
+ private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet) throws
+ PackageManagerException {
+ if (copyRet < 0) {
+ if (copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
+ copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
+ throw new PackageManagerException(copyRet, message);
+ }
+ }
+ }
+
/**
* Extract the MountService "container ID" from the full code path of an
* .apk.
@@ -9811,6 +9790,16 @@
return prefix + idxStr;
}
+ private File getNextCodePath(String packageName) {
+ int suffix = 1;
+ File result;
+ do {
+ result = new File(mAppInstallDir, packageName + "-" + suffix);
+ suffix++;
+ } while (result.exists());
+ return result;
+ }
+
// Utility method used to ignore ADD/REMOVE events
// by directory observer.
private static boolean ignoreCodePath(String fullPathStr) {
@@ -13255,4 +13244,83 @@
}
return mUserNeedsBadging.valueAt(index);
}
+
+ @Override
+ public KeySetHandle getKeySetByAlias(String packageName, String alias) {
+ if (packageName == null || alias == null) {
+ return null;
+ }
+ synchronized(mPackages) {
+ final PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ if (pkg.applicationInfo.uid != Binder.getCallingUid()
+ && Process.SYSTEM_UID != Binder.getCallingUid()) {
+ throw new SecurityException("May not access KeySets defined by"
+ + " aliases in other applications.");
+ }
+ KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ return ksms.getKeySetByAliasAndPackageNameLPr(packageName, alias);
+ }
+ }
+
+ @Override
+ public KeySetHandle getSigningKeySet(String packageName) {
+ if (packageName == null) {
+ return null;
+ }
+ synchronized(mPackages) {
+ final PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ if (pkg.applicationInfo.uid != Binder.getCallingUid()
+ && Process.SYSTEM_UID != Binder.getCallingUid()) {
+ throw new SecurityException("May not access signing KeySet of other apps.");
+ }
+ KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ return ksms.getSigningKeySetByPackageNameLPr(packageName);
+ }
+ }
+
+ @Override
+ public boolean isPackageSignedByKeySet(String packageName, IBinder ks) {
+ if (packageName == null || ks == null) {
+ return false;
+ }
+ synchronized(mPackages) {
+ final PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ if (ks instanceof KeySetHandle) {
+ KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ return ksms.packageIsSignedByLPr(packageName, (KeySetHandle) ks);
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isPackageSignedByKeySetExactly(String packageName, IBinder ks) {
+ if (packageName == null || ks == null) {
+ return false;
+ }
+ synchronized(mPackages) {
+ final PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ Slog.w(TAG, "KeySet requested for unknown package:" + packageName);
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ if (ks instanceof KeySetHandle) {
+ KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ return ksms.packageIsSignedByExactlyLPr(packageName, (KeySetHandle) ks);
+ }
+ return false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a69dd30..ebad422 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -58,6 +58,8 @@
import android.os.SystemService;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.os.Parcel;
+import android.os.ServiceManager;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.util.EventLog;
@@ -659,6 +661,7 @@
if (mLowPowerModeEnabled != lowPowerModeEnabled) {
mLowPowerModeEnabled = lowPowerModeEnabled;
powerHintInternal(POWER_HINT_LOW_POWER_MODE, lowPowerModeEnabled ? 1 : 0);
+ setSurfaceFlingerLowPowerMode(lowPowerModeEnabled ? 1 : 0);
mLowPowerModeEnabled = lowPowerModeEnabled;
BackgroundThread.getHandler().post(new Runnable() {
@Override
@@ -2094,6 +2097,21 @@
nativeSendPowerHint(hintId, data);
}
+ private static void setSurfaceFlingerLowPowerMode(int enabled) {
+ try {
+ final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+ if (flinger != null) {
+ final Parcel data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ data.writeInt(enabled);
+ flinger.transact(1016, data, null, 0);
+ data.recycle();
+ }
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to reduce refresh rate", ex);
+ }
+ }
+
/**
* Low-level function turn the device off immediately, without trying
* to be clean. Most people should use {@link ShutdownThread} for a clean shutdown.
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index efe543b..8f237db 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -16,7 +16,12 @@
package com.android.server.tv;
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
import android.content.Context;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiHotplugEvent;
import android.media.AudioDevicePort;
import android.media.AudioManager;
import android.media.AudioPatch;
@@ -25,14 +30,22 @@
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
import android.media.tv.TvInputHardwareInfo;
+import android.media.tv.TvInputInfo;
import android.media.tv.TvStreamConfig;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.Surface;
+import com.android.server.SystemService;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -46,23 +59,42 @@
*
* @hide
*/
-class TvInputHardwareManager implements TvInputHal.Callback {
+class TvInputHardwareManager
+ implements TvInputHal.Callback, HdmiControlManager.HotplugEventListener {
private static final String TAG = TvInputHardwareManager.class.getSimpleName();
private final TvInputHal mHal = new TvInputHal(this);
private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
private final Context mContext;
+ private final TvInputManagerService.Client mClient;
private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
private final AudioManager mAudioManager;
+ private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
+ // TODO: Should handle INACTIVE case.
+ private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>();
+
+ // Calls to mClient should happen here.
+ private final HandlerThread mHandlerThread = new HandlerThread(TAG);
+ private final Handler mHandler;
private final Object mLock = new Object();
- public TvInputHardwareManager(Context context) {
+ public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
mContext = context;
+ mClient = client;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
- // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
mHal.init();
+
+ mHandlerThread.start();
+ mHandler = new ClientHandler(mHandlerThread.getLooper());
+ }
+
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ HdmiControlManager hdmiControlManager =
+ (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
+ hdmiControlManager.addHotplugEventListener(this);
+ }
}
@Override
@@ -80,7 +112,7 @@
private void buildInfoListLocked() {
mInfoList.clear();
for (int i = 0; i < mConnections.size(); ++i) {
- mInfoList.add(mConnections.valueAt(i).getInfoLocked());
+ mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
}
}
@@ -92,7 +124,7 @@
Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
return;
}
- connection.resetLocked(null, null, null, null);
+ connection.resetLocked(null, null, null, null, null);
mConnections.remove(deviceId);
buildInfoListLocked();
// TODO: notify if necessary
@@ -136,6 +168,37 @@
return false;
}
+ private int convertConnectedToState(boolean connected) {
+ if (connected) {
+ return INPUT_STATE_CONNECTED;
+ } else {
+ return INPUT_STATE_DISCONNECTED;
+ }
+ }
+
+ public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+ if (info.getType() == TvInputInfo.TYPE_VIRTUAL) {
+ throw new IllegalArgumentException("info (" + info + ") has virtual type.");
+ }
+ synchronized (mLock) {
+ if (mTvInputInfoMap.indexOfKey(deviceId) >= 0) {
+ Slog.w(TAG, "Trying to override previous registration: old = "
+ + mTvInputInfoMap.get(deviceId) + ":" + deviceId + ", new = "
+ + info + ":" + deviceId);
+ }
+ mTvInputInfoMap.put(deviceId, info);
+
+ for (int i = 0; i < mHdmiStateMap.size(); ++i) {
+ String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
+ if (inputId != null && inputId.equals(info.getId())) {
+ mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+ convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
+ inputId).sendToTarget();
+ }
+ }
+ }
+ }
+
/**
* Create a TvInputHardware object with a specific deviceId. One service at a time can access
* the object, and if more than one process attempts to create hardware with the same deviceId,
@@ -143,7 +206,7 @@
* release is notified via ITvInputHardwareCallback.onReleased().
*/
public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
- int callingUid, int resolvedUserId) {
+ TvInputInfo info, int callingUid, int resolvedUserId) {
if (callback == null) {
throw new NullPointerException();
}
@@ -154,14 +217,15 @@
return null;
}
if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
- TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
+ TvInputHardwareImpl hardware =
+ new TvInputHardwareImpl(connection.getHardwareInfoLocked());
try {
callback.asBinder().linkToDeath(connection, 0);
} catch (RemoteException e) {
hardware.release();
return null;
}
- connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
+ connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
}
return connection.getHardwareLocked();
}
@@ -182,26 +246,55 @@
|| checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
return;
}
- connection.resetLocked(null, null, null, null);
+ connection.resetLocked(null, null, null, null, null);
+ }
+ }
+
+ private String findInputIdForHdmiPortLocked(int port) {
+ for (TvInputHardwareInfo hardwareInfo : mInfoList) {
+ if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
+ && hardwareInfo.getHdmiPortId() == port) {
+ TvInputInfo info = mTvInputInfoMap.get(hardwareInfo.getDeviceId());
+ return (info == null) ? null : info.getId();
+ }
+ }
+ return null;
+ }
+
+ // HdmiControlManager.HotplugEventListener implementation.
+
+ @Override
+ public void onReceived(HdmiHotplugEvent event) {
+ String inputId = null;
+
+ synchronized (mLock) {
+ mHdmiStateMap.put(event.getPort(), event.isConnected());
+ inputId = findInputIdForHdmiPortLocked(event.getPort());
+ if (inputId == null) {
+ return;
+ }
+ mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
+ convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
}
}
private class Connection implements IBinder.DeathRecipient {
- private final TvInputHardwareInfo mInfo;
+ private final TvInputHardwareInfo mHardwareInfo;
+ private TvInputInfo mInfo;
private TvInputHardwareImpl mHardware = null;
private ITvInputHardwareCallback mCallback;
private TvStreamConfig[] mConfigs = null;
private Integer mCallingUid = null;
private Integer mResolvedUserId = null;
- public Connection(TvInputHardwareInfo info) {
- mInfo = info;
+ public Connection(TvInputHardwareInfo hardwareInfo) {
+ mHardwareInfo = hardwareInfo;
}
// *Locked methods assume TvInputHardwareManager.mLock is held.
- public void resetLocked(TvInputHardwareImpl hardware,
- ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
+ public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
+ TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
if (mHardware != null) {
try {
mCallback.onReleased();
@@ -212,6 +305,7 @@
}
mHardware = hardware;
mCallback = callback;
+ mInfo = info;
mCallingUid = callingUid;
mResolvedUserId = resolvedUserId;
@@ -228,7 +322,11 @@
mConfigs = configs;
}
- public TvInputHardwareInfo getInfoLocked() {
+ public TvInputHardwareInfo getHardwareInfoLocked() {
+ return mHardwareInfo;
+ }
+
+ public TvInputInfo getInfoLocked() {
return mInfo;
}
@@ -255,7 +353,7 @@
@Override
public void binderDied() {
synchronized (mLock) {
- resetLocked(null, null, null, null);
+ resetLocked(null, null, null, null, null);
}
}
}
@@ -403,4 +501,28 @@
return false;
}
}
+
+ private class ClientHandler extends Handler {
+ private static final int DO_SET_AVAILABLE = 1;
+
+ ClientHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public final void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DO_SET_AVAILABLE: {
+ String inputId = (String) msg.obj;
+ int state = msg.arg1;
+ mClient.setState(inputId, state);
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Unhandled message: " + msg);
+ break;
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5e95af4..20fdefa 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -16,6 +16,9 @@
package com.android.server.tv;
+import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -38,6 +41,7 @@
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
import android.media.tv.ITvInputManager;
+import android.media.tv.ITvInputManagerCallback;
import android.media.tv.ITvInputService;
import android.media.tv.ITvInputServiceCallback;
import android.media.tv.ITvInputSession;
@@ -109,7 +113,7 @@
mContentResolver = context.getContentResolver();
mLogHandler = new LogHandler(IoThread.get().getLooper());
- mTvInputHardwareManager = new TvInputHardwareManager(context);
+ mTvInputHardwareManager = new TvInputHardwareManager(context, new Client());
synchronized (mLock) {
mUserStates.put(mCurrentUserId, new UserState());
@@ -129,6 +133,7 @@
buildTvInputListLocked(mCurrentUserId);
}
}
+ mTvInputHardwareManager.onBootPhase(phase);
}
private void registerBroadcastReceivers() {
@@ -144,7 +149,7 @@
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
UserState userState = getUserStateLocked(mCurrentUserId);
- if (!userState.packageList.contains(packageName)) {
+ if (!userState.packageSet.contains(packageName)) {
// Not a TV input package.
return;
}
@@ -198,8 +203,11 @@
private void buildTvInputListLocked(int userId) {
UserState userState = getUserStateLocked(userId);
- userState.inputMap.clear();
- userState.packageList.clear();
+
+ Map<String, TvInputState> oldInputMap = userState.inputMap;
+ userState.inputMap = new HashMap<String, TvInputState>();
+
+ userState.packageSet.clear();
if (DEBUG) Slog.d(TAG, "buildTvInputList");
PackageManager pm = mContext.getPackageManager();
@@ -216,8 +224,13 @@
try {
TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
if (DEBUG) Slog.d(TAG, "add " + info.getId());
- userState.inputMap.put(info.getId(), info);
- userState.packageList.add(si.packageName);
+ TvInputState state = oldInputMap.get(info.getId());
+ if (state == null) {
+ state = new TvInputState();
+ }
+ userState.inputMap.put(info.getId(), state);
+ state.mInfo = info;
+ userState.packageSet.add(si.packageName);
// Reconnect the service if existing input is updated.
updateServiceConnectionLocked(info.getId(), userId);
@@ -225,6 +238,7 @@
Slog.e(TAG, "Can't load TV input " + si.name, e);
}
}
+ oldInputMap.clear();
}
private void switchUser(int userId) {
@@ -359,7 +373,7 @@
}
Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
- userState.inputMap.get(inputId).getComponent());
+ userState.inputMap.get(inputId).mInfo.getComponent());
// Binding service may fail if the service is updating.
// In that case, the connection will be revived in buildTvInputListLocked called by
// onSomePackagesChanged.
@@ -588,7 +602,7 @@
updateServiceConnectionLocked(sessionState.mInputId, userId);
}
- private void unregisterCallbackInternalLocked(IBinder clientToken, String inputId,
+ private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
int userId) {
UserState userState = getUserStateLocked(userId);
ClientState clientState = userState.clientStateMap.get(clientToken);
@@ -623,14 +637,43 @@
}
}
- private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
- for (IBinder clientToken : serviceState.mClientTokens) {
- try {
- ITvInputClient.Stub.asInterface(clientToken).onAvailabilityChanged(
- serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
- } catch (RemoteException e) {
- Slog.e(TAG, "error in onAvailabilityChanged", e);
+ private void notifyStateChangedLocked(UserState userState, String inputId,
+ int state, ITvInputManagerCallback targetCallback) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyStateChangedLocked: inputId = " + inputId
+ + "; state = " + state);
+ }
+ if (targetCallback == null) {
+ for (ITvInputManagerCallback callback : userState.callbackSet) {
+ try {
+ callback.onInputStateChanged(inputId, state);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report state change to callback.");
+ }
}
+ } else {
+ try {
+ targetCallback.onInputStateChanged(inputId, state);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report state change to callback.");
+ }
+ }
+ }
+
+ private void setStateLocked(String inputId, int state, int userId) {
+ UserState userState = getUserStateLocked(userId);
+ TvInputState inputState = userState.inputMap.get(inputId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
+ int oldState = inputState.mState;
+ inputState.mState = state;
+ boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
+ && serviceState.mSessionTokens.isEmpty();
+ if (serviceState != null && serviceState.mService == null && !isStateEmpty) {
+ // We don't notify state change while reconnecting. It should remain disconnected.
+ return;
+ }
+ if (oldState != state) {
+ notifyStateChangedLocked(userState, inputId, state, null);
}
}
@@ -643,80 +686,29 @@
try {
synchronized (mLock) {
UserState userState = getUserStateLocked(resolvedUserId);
- return new ArrayList<TvInputInfo>(userState.inputMap.values());
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public boolean getAvailability(final ITvInputClient client, final String inputId,
- int userId) {
- final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, "getAvailability");
- final long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- UserState userState = getUserStateLocked(resolvedUserId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
- if (serviceState != null) {
- // We already know the status of this input service. Return the cached
- // status.
- return serviceState.mAvailable;
+ List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
+ for (TvInputState state : userState.inputMap.values()) {
+ inputList.add(state.mInfo);
}
+ return inputList;
}
} finally {
Binder.restoreCallingIdentity(identity);
}
- // STOPSHIP: Redesign the API around the availability change. For now, the service
- // will be always available.
- return true;
}
@Override
- public void registerCallback(final ITvInputClient client, final String inputId,
- int userId) {
+ public void registerCallback(final ITvInputManagerCallback callback, int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Binder.getCallingUid(), userId, "registerCallback");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // Create a new service callback and add it to the callback map of the current
- // service.
UserState userState = getUserStateLocked(resolvedUserId);
- ServiceState serviceState = userState.serviceStateMap.get(inputId);
- if (serviceState == null) {
- serviceState = new ServiceState(
- userState.inputMap.get(inputId), resolvedUserId);
- userState.serviceStateMap.put(inputId, serviceState);
- }
- IBinder clientToken = client.asBinder();
- if (!serviceState.mClientTokens.contains(clientToken)) {
- serviceState.mClientTokens.add(clientToken);
- }
-
- ClientState clientState = userState.clientStateMap.get(clientToken);
- if (clientState == null) {
- clientState = createClientStateLocked(clientToken, resolvedUserId);
- }
- if (!clientState.mInputIds.contains(inputId)) {
- clientState.mInputIds.add(inputId);
- }
-
- if (serviceState.mService != null) {
- if (serviceState.mCallback != null) {
- // We already handled.
- return;
- }
- serviceState.mCallback = new ServiceCallback(resolvedUserId);
- try {
- serviceState.mService.registerCallback(serviceState.mCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "error in registerCallback", e);
- }
- } else {
- updateServiceConnectionLocked(inputId, resolvedUserId);
+ userState.callbackSet.add(callback);
+ for (TvInputState state : userState.inputMap.values()) {
+ notifyStateChangedLocked(userState, state.mInfo.getId(),
+ state.mState, callback);
}
}
} finally {
@@ -725,13 +717,14 @@
}
@Override
- public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
+ public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
Binder.getCallingUid(), userId, "unregisterCallback");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- unregisterCallbackInternalLocked(client.asBinder(), inputId, resolvedUserId);
+ UserState userState = getUserStateLocked(resolvedUserId);
+ userState.callbackSet.remove(callback);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -751,7 +744,7 @@
ServiceState serviceState = userState.serviceStateMap.get(inputId);
if (serviceState == null) {
serviceState = new ServiceState(
- userState.inputMap.get(inputId), resolvedUserId);
+ userState.inputMap.get(inputId).mInfo, resolvedUserId);
userState.serviceStateMap.put(inputId, serviceState);
}
// Send a null token immediately while reconnecting.
@@ -868,7 +861,7 @@
}
// Create a log entry and fill it later.
- String packageName = userState.inputMap.get(sessionState.mInputId)
+ String packageName = userState.inputMap.get(sessionState.mInputId).mInfo
.getServiceInfo().packageName;
ContentValues values = new ContentValues();
values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
@@ -1017,8 +1010,7 @@
@Override
public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.TV_INPUT_HARDWARE)
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
return null;
}
@@ -1032,10 +1024,25 @@
}
@Override
+ public void registerTvInputInfo(TvInputInfo info, int deviceId) {
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
+ != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mTvInputHardwareManager.registerTvInputInfo(info, deviceId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public ITvInputHardware acquireTvInputHardware(int deviceId,
- ITvInputHardwareCallback callback, int userId) throws RemoteException {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.TV_INPUT_HARDWARE)
+ ITvInputHardwareCallback callback, TvInputInfo info, int userId)
+ throws RemoteException {
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
return null;
}
@@ -1046,7 +1053,7 @@
userId, "acquireTvInputHardware");
try {
return mTvInputHardwareManager.acquireHardware(
- deviceId, callback, callingUid, resolvedUserId);
+ deviceId, callback, info, callingUid, resolvedUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1055,8 +1062,7 @@
@Override
public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
throws RemoteException {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.TV_INPUT_HARDWARE)
+ if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
@@ -1099,16 +1105,16 @@
pw.println("UserState (" + userId + "):");
pw.increaseIndent();
- pw.println("inputMap: inputId -> TvInputInfo");
+ pw.println("inputMap: inputId -> TvInputState");
pw.increaseIndent();
- for (TvInputInfo info : userState.inputMap.values()) {
- pw.println(info.toString());
+ for (TvInputState state : userState.inputMap.values()) {
+ pw.println(state.toString());
}
pw.decreaseIndent();
- pw.println("packageList:");
+ pw.println("packageSet:");
pw.increaseIndent();
- for (String packageName : userState.packageList) {
+ for (String packageName : userState.packageSet) {
pw.println(packageName);
}
pw.decreaseIndent();
@@ -1169,7 +1175,6 @@
pw.println("mService: " + service.mService);
pw.println("mCallback: " + service.mCallback);
pw.println("mBound: " + service.mBound);
- pw.println("mAvailable: " + service.mAvailable);
pw.println("mReconnecting: " + service.mReconnecting);
pw.decreaseIndent();
@@ -1196,18 +1201,38 @@
}
pw.decreaseIndent();
+ pw.println("callbackSet:");
+ pw.increaseIndent();
+ for (ITvInputManagerCallback callback : userState.callbackSet) {
+ pw.println(callback.toString());
+ }
+ pw.decreaseIndent();
+
pw.decreaseIndent();
}
}
}
}
- private static final class UserState {
- // A mapping from the TV input id to its TvInputInfo.
- private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
+ private static final class TvInputState {
+ // A TvInputInfo object which represents the TV input.
+ private TvInputInfo mInfo;
- // A list of all TV input packages.
- private final Set<String> packageList = new HashSet<String>();
+ // The state of TV input. Connected by default.
+ private int mState = INPUT_STATE_CONNECTED;
+
+ @Override
+ public String toString() {
+ return "mInfo: " + mInfo + "; mState: " + mState;
+ }
+ }
+
+ private static final class UserState {
+ // A mapping from the TV input id to its TvInputState.
+ private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
+
+ // A set of all TV input packages.
+ private final Set<String> packageSet = new HashSet<String>();
// A mapping from the token of a client to its state.
private final Map<IBinder, ClientState> clientStateMap =
@@ -1220,6 +1245,10 @@
// A mapping from the token of a TV input session to its state.
private final Map<IBinder, SessionState> sessionStateMap =
new HashMap<IBinder, SessionState>();
+
+ // A set of callbacks.
+ private final Set<ITvInputManagerCallback> callbackSet =
+ new HashSet<ITvInputManagerCallback>();
}
private final class ClientState implements IBinder.DeathRecipient {
@@ -1243,7 +1272,7 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(mUserId);
// DO NOT remove the client state of clientStateMap in this method. It will be
- // removed in releaseSessionLocked() or unregisterCallbackInternalLocked().
+ // removed in releaseSessionLocked() or unregisterClientInternalLocked().
ClientState clientState = userState.clientStateMap.get(mClientToken);
if (clientState != null) {
while (clientState.mSessionTokens.size() > 0) {
@@ -1251,7 +1280,7 @@
clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
}
while (clientState.mInputIds.size() > 0) {
- unregisterCallbackInternalLocked(
+ unregisterClientInternalLocked(
mClientToken, clientState.mInputIds.get(0), mUserId);
}
}
@@ -1269,7 +1298,6 @@
private ITvInputService mService;
private ServiceCallback mCallback;
private boolean mBound;
- private boolean mAvailable;
private boolean mReconnecting;
private ServiceState(TvInputInfo inputInfo, int userId) {
@@ -1325,16 +1353,18 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
+ String inputId = mTvInputInfo.getId();
if (DEBUG) {
- Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
+ Slog.d(TAG, "onServiceConnected(inputId=" + inputId + ")");
}
synchronized (mLock) {
- ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
+ UserState userState = getUserStateLocked(mUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(inputId);
serviceState.mService = ITvInputService.Stub.asInterface(service);
// Register a callback, if we need to.
if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
- serviceState.mCallback = new ServiceCallback(mUserId);
+ serviceState.mCallback = new ServiceCallback(mTvInputInfo.getId(), mUserId);
try {
serviceState.mService.registerCallback(serviceState.mCallback);
} catch (RemoteException e) {
@@ -1346,6 +1376,12 @@
for (IBinder sessionToken : serviceState.mSessionTokens) {
createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
}
+
+ TvInputState inputState = userState.inputMap.get(inputId);
+ if (inputState != null && inputState.mState != INPUT_STATE_DISCONNECTED) {
+ notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+ inputState.mState, null);
+ }
}
}
@@ -1377,10 +1413,8 @@
}
}
- if (serviceState.mAvailable) {
- serviceState.mAvailable = false;
- broadcastServiceAvailabilityChangedLocked(serviceState);
- }
+ notifyStateChangedLocked(userState, mTvInputInfo.getId(),
+ INPUT_STATE_DISCONNECTED, null);
updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
}
}
@@ -1388,24 +1422,22 @@
}
private final class ServiceCallback extends ITvInputServiceCallback.Stub {
+ private final String mInputId;
private final int mUserId;
- ServiceCallback(int userId) {
+ ServiceCallback(String inputId, int userId) {
+ mInputId = inputId;
mUserId = userId;
}
@Override
- public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+ public void onInputStateChanged(int state) {
if (DEBUG) {
- Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
- + isAvailable + ")");
+ Slog.d(TAG, "onInputStateChanged(inputId=" + mInputId + ", state="
+ + state + ")");
}
synchronized (mLock) {
- ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
- if (serviceState.mAvailable != isAvailable) {
- serviceState.mAvailable = isAvailable;
- broadcastServiceAvailabilityChangedLocked(serviceState);
- }
+ setStateLocked(mInputId, state, mUserId);
}
}
}
@@ -1553,4 +1585,12 @@
mContentResolver.update(uri, values, null, null);
}
}
+
+ final class Client {
+ public void setState(String inputId, int state) {
+ synchronized (mLock) {
+ setStateLocked(inputId, state, mCurrentUserId);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index e007600..0e1340c 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -46,6 +46,8 @@
import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindBackgroundAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
@@ -72,44 +74,42 @@
WindowManagerService.DEBUG_APP_TRANSITIONS;
private static final boolean DEBUG_ANIM = WindowManagerService.DEBUG_ANIM;
- /** Bit mask that is set for all enter transition. */
- public static final int TRANSIT_ENTER_MASK = 0x1000;
-
- /** Bit mask that is set for all exit transitions. */
- public static final int TRANSIT_EXIT_MASK = 0x2000;
/** Not set up for a transition. */
public static final int TRANSIT_UNSET = -1;
/** No animation for transition. */
public static final int TRANSIT_NONE = 0;
/** A window in a new activity is being opened on top of an existing one in the same task. */
- public static final int TRANSIT_ACTIVITY_OPEN = 6 | TRANSIT_ENTER_MASK;
+ public static final int TRANSIT_ACTIVITY_OPEN = 6;
/** The window in the top-most activity is being closed to reveal the
* previous activity in the same task. */
- public static final int TRANSIT_ACTIVITY_CLOSE = 7 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_ACTIVITY_CLOSE = 7;
/** A window in a new task is being opened on top of an existing one
* in another activity's task. */
- public static final int TRANSIT_TASK_OPEN = 8 | TRANSIT_ENTER_MASK;
+ public static final int TRANSIT_TASK_OPEN = 8;
/** A window in the top-most activity is being closed to reveal the
* previous activity in a different task. */
- public static final int TRANSIT_TASK_CLOSE = 9 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_TASK_CLOSE = 9;
/** A window in an existing task is being displayed on top of an existing one
* in another activity's task. */
- public static final int TRANSIT_TASK_TO_FRONT = 10 | TRANSIT_ENTER_MASK;
+ public static final int TRANSIT_TASK_TO_FRONT = 10;
/** A window in an existing task is being put below all other tasks. */
- public static final int TRANSIT_TASK_TO_BACK = 11 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_TASK_TO_BACK = 11;
/** A window in a new activity that doesn't have a wallpaper is being opened on top of one that
* does, effectively closing the wallpaper. */
- public static final int TRANSIT_WALLPAPER_CLOSE = 12 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_WALLPAPER_CLOSE = 12;
/** A window in a new activity that does have a wallpaper is being opened on one that didn't,
* effectively opening the wallpaper. */
- public static final int TRANSIT_WALLPAPER_OPEN = 13 | TRANSIT_ENTER_MASK;
+ public static final int TRANSIT_WALLPAPER_OPEN = 13;
/** A window in a new activity is being opened on top of an existing one, and both are on top
* of the wallpaper. */
- public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14 | TRANSIT_ENTER_MASK;
+ public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14;
/** The window in the top-most activity is being closed to reveal the previous activity, and
* both are on top of the wallpaper. */
- public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15;
+ /** A window in a new task is being opened behind an existing one in another activity's task.
+ * The new window will show briefly and then be gone. */
+ public static final int TRANSIT_TASK_OPEN_BEHIND = 16;
/** Fraction of animation at which the recents thumbnail becomes completely transparent */
private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f;
@@ -811,6 +811,10 @@
? WindowAnimation_wallpaperIntraCloseEnterAnimation
: WindowAnimation_wallpaperIntraCloseExitAnimation;
break;
+ case TRANSIT_TASK_OPEN_BEHIND:
+ animAttr = enter
+ ? WindowAnimation_launchTaskBehindSourceAnimation
+ : WindowAnimation_launchTaskBehindBackgroundAnimation;
}
a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null;
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
@@ -896,9 +900,6 @@
case TRANSIT_NONE: {
return "TRANSIT_NONE";
}
- case TRANSIT_EXIT_MASK: {
- return "TRANSIT_EXIT_MASK";
- }
case TRANSIT_ACTIVITY_OPEN: {
return "TRANSIT_ACTIVITY_OPEN";
}
@@ -929,6 +930,9 @@
case TRANSIT_WALLPAPER_INTRA_CLOSE: {
return "TRANSIT_WALLPAPER_INTRA_CLOSE";
}
+ case TRANSIT_TASK_OPEN_BEHIND: {
+ return "TRANSIT_TASK_OPEN_BEHIND";
+ }
default: {
return "<UNKNOWN>";
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 63ae98e..874e105 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -16,7 +16,9 @@
package com.android.server.wm;
+import android.graphics.Bitmap;
import android.graphics.Matrix;
+import android.os.RemoteException;
import android.util.Slog;
import android.util.TimeUtils;
import android.view.Display;
@@ -281,9 +283,21 @@
final int N = mAllAppWinAnimators.size();
for (int i=0; i<N; i++) {
- mAllAppWinAnimators.get(i).finishExit();
+ final WindowStateAnimator winAnim = mAllAppWinAnimators.get(i);
+ if (mAppToken.mLaunchTaskBehind) {
+ winAnim.mWin.mExiting = true;
+ }
+ winAnim.finishExit();
}
- mAppToken.updateReportedVisibilityLocked();
+ if (mAppToken.mLaunchTaskBehind) {
+ try {
+ mService.mActivityManager.notifyLaunchTaskBehindComplete(mAppToken.token);
+ } catch (RemoteException e) {
+ }
+ mAppToken.mLaunchTaskBehind = false;
+ } else {
+ mAppToken.updateReportedVisibilityLocked();
+ }
return false;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 12c15e2..312689b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -109,6 +109,8 @@
boolean mDeferRemoval;
+ boolean mLaunchTaskBehind;
+
AppWindowToken(WindowManagerService _service, IApplicationToken _token,
boolean _voiceInteraction) {
super(_service, _token.asBinder(),
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 396ec8f..a5959d4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -497,8 +497,8 @@
boolean mStartingIconInTransition = false;
boolean mSkipAppTransitionAnimation = false;
- final ArrayList<AppWindowToken> mOpeningApps = new ArrayList<AppWindowToken>();
- final ArrayList<AppWindowToken> mClosingApps = new ArrayList<AppWindowToken>();
+ final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<AppWindowToken>();
+ final ArraySet<AppWindowToken> mClosingApps = new ArraySet<AppWindowToken>();
boolean mIsTouchDevice;
@@ -3234,6 +3234,12 @@
SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN);
}
+ if (atoken.mLaunchTaskBehind) {
+ // Differentiate the two animations. This one which is briefly on the screen
+ // gets the !enter animation, and the other activity which remains on the
+ // screen gets the enter animation. Both appear in the mOpeningApps set.
+ enter = false;
+ }
Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen,
isVoiceInteraction);
@@ -3449,14 +3455,14 @@
EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
Task task = new Task(atoken, stack, userId);
mTaskIdToTask.put(taskId, task);
- stack.addTask(task, true);
+ stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */);
return task;
}
@Override
public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
- int configChanges, boolean voiceInteraction) {
+ int configChanges, boolean voiceInteraction, boolean launchTaskBehind) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3490,6 +3496,7 @@
atoken.requestedOrientation = requestedOrientation;
atoken.layoutConfigChanges = (configChanges &
(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
+ atoken.mLaunchTaskBehind = launchTaskBehind;
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken
+ " to stack=" + stackId + " task=" + taskId + " at " + addPos);
@@ -3954,16 +3961,16 @@
}
synchronized(mWindowMap) {
- if (DEBUG_APP_TRANSITIONS) {
- RuntimeException e = new RuntimeException("here");
- e.fillInStackTrace();
- Slog.w(TAG, "Execute app transition: " + mAppTransition, e);
- }
+ if (DEBUG_APP_TRANSITIONS) Slog.w(TAG, "Execute app transition: " + mAppTransition,
+ new RuntimeException("here").fillInStackTrace());
if (mAppTransition.isTransitionSet()) {
mAppTransition.setReady();
final long origId = Binder.clearCallingIdentity();
- performLayoutAndPlaceSurfacesLocked();
- Binder.restoreCallingIdentity(origId);
+ try {
+ performLayoutAndPlaceSurfacesLocked();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
}
}
@@ -4370,17 +4377,11 @@
return;
}
- if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) {
- RuntimeException e = null;
- if (!HIDE_STACK_CRAWLS) {
- e = new RuntimeException();
- e.fillInStackTrace();
- }
- Slog.v(TAG, "setAppVisibility(" + token + ", visible=" + visible
- + "): " + mAppTransition
- + " hidden=" + wtoken.hidden
- + " hiddenRequested=" + wtoken.hiddenRequested, e);
- }
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG, "setAppVisibility(" +
+ token + ", visible=" + visible + "): " + mAppTransition +
+ " hidden=" + wtoken.hidden + " hiddenRequested=" +
+ wtoken.hiddenRequested, HIDE_STACK_CRAWLS ?
+ null : new RuntimeException("here").fillInStackTrace());
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
@@ -4428,6 +4429,21 @@
wtoken.waitingToHide = true;
}
}
+ if (mAppTransition.getAppTransition() == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
+ // We're launchingBehind, add the launching activity to mOpeningApps.
+ final WindowState win =
+ findFocusedWindowLocked(getDefaultDisplayContentLocked());
+ if (win != null) {
+ final AppWindowToken focusedToken = win.mAppToken;
+ if (focusedToken != null) {
+ if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "TRANSIT_TASK_OPEN_BEHIND, " +
+ " adding " + focusedToken + " to mOpeningApps");
+ // Force animation to be loaded.
+ focusedToken.hidden = true;
+ mOpeningApps.add(focusedToken);
+ }
+ }
+ }
return;
}
@@ -8558,7 +8574,7 @@
// all of the apps are ready. Otherwise just go because
// we'll unfreeze the display when everyone is ready.
for (i=0; i<NN && goodToGo; i++) {
- AppWindowToken wtoken = mOpeningApps.get(i);
+ AppWindowToken wtoken = mOpeningApps.valueAt(i);
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Check opening app=" + wtoken + ": allDrawn="
+ wtoken.allDrawn + " startingDisplayed="
@@ -8631,12 +8647,12 @@
for (i=0; i<NN; i++) {
final AppWindowToken wtoken;
if (i < NC) {
- wtoken = mClosingApps.get(i);
+ wtoken = mClosingApps.valueAt(i);
if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
closingAppHasWallpaper = true;
}
} else {
- wtoken = mOpeningApps.get(i - NC);
+ wtoken = mOpeningApps.valueAt(i - NC);
if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
openingAppHasWallpaper = true;
}
@@ -8710,7 +8726,7 @@
NN = mOpeningApps.size();
for (i=0; i<NN; i++) {
- AppWindowToken wtoken = mOpeningApps.get(i);
+ AppWindowToken wtoken = mOpeningApps.valueAt(i);
final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
appAnimator.clearThumbnail();
@@ -8743,7 +8759,7 @@
}
NN = mClosingApps.size();
for (i=0; i<NN; i++) {
- AppWindowToken wtoken = mClosingApps.get(i);
+ AppWindowToken wtoken = mClosingApps.valueAt(i);
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
wtoken.mAppAnimator.clearThumbnail();
wtoken.inPendingTransaction = false;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 8387b65..b24072f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -717,10 +717,10 @@
float top = w.mFrame.top + w.mYOffset;
// Adjust for surface insets.
- width += attrs.shadowInsets.left + attrs.shadowInsets.right;
- height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
- left -= attrs.shadowInsets.left;
- top -= attrs.shadowInsets.top;
+ width += attrs.surfaceInsets.left + attrs.surfaceInsets.right;
+ height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
+ left -= attrs.surfaceInsets.left;
+ top -= attrs.surfaceInsets.top;
if (DEBUG_VISIBILITY) {
Slog.v(TAG, "Creating surface in session "
@@ -1140,19 +1140,12 @@
void applyDecorRect(final Rect decorRect) {
final WindowState w = mWin;
- int width = w.mFrame.width();
- int height = w.mFrame.height();
+ final int width = w.mFrame.width();
+ final int height = w.mFrame.height();
// Compute the offset of the window in relation to the decor rect.
- int left = w.mXOffset + w.mFrame.left;
- int top = w.mYOffset + w.mFrame.top;
-
- // Adjust for surface insets.
- final WindowManager.LayoutParams attrs = w.mAttrs;
- width += attrs.shadowInsets.left + attrs.shadowInsets.right;
- height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
- left -= attrs.shadowInsets.left;
- top -= attrs.shadowInsets.top;
+ final int left = w.mXOffset + w.mFrame.left;
+ final int top = w.mYOffset + w.mFrame.top;
// Initialize the decor rect to the entire frame.
w.mSystemDecorRect.set(0, 0, width, height);
@@ -1182,7 +1175,6 @@
if (displayContent == null) {
return;
}
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
// Need to recompute a new system decor rect each time.
if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
@@ -1192,6 +1184,7 @@
} else if (!w.isDefaultDisplay()) {
// On a different display there is no system decor. Crop the window
// by the screen boundaries.
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
w.mSystemDecorRect.intersect(-w.mCompatFrame.left, -w.mCompatFrame.top,
displayInfo.logicalWidth - w.mCompatFrame.left,
@@ -1202,44 +1195,52 @@
// windows need to be cropped by the screen, so they don't cover
// the universe background.
if (mAnimator.mUniverseBackground == null) {
- w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(),
- w.mCompatFrame.height());
+ w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
} else {
applyDecorRect(mService.mScreenRect);
}
} else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
|| w.mDecorFrame.isEmpty()) {
// The universe background isn't cropped, nor windows without policy decor.
- w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(),
- w.mCompatFrame.height());
+ w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
} else {
// Crop to the system decor specified by policy.
applyDecorRect(w.mDecorFrame);
}
- // By default, the clip rect is the system decor rect
- Rect clipRect = w.mSystemDecorRect;
- if (mHasClipRect) {
+ // By default, the clip rect is the system decor.
+ final Rect clipRect = mTmpClipRect;
+ clipRect.set(w.mSystemDecorRect);
- // If we have an animated clip rect, intersect it with the system decor rect
- // NOTE: We are adding a temporary workaround due to the status bar not always reporting
- // the correct system decor rect. In such cases, we take into account the specified
- // content insets as well.
- int offsetTop = Math.max(w.mSystemDecorRect.top, w.mContentInsets.top);
- mTmpClipRect.set(w.mSystemDecorRect);
- // Don't apply the workaround to apps explicitly requesting fullscreen layout.
+ // Expand the clip rect for surface insets.
+ final WindowManager.LayoutParams attrs = w.mAttrs;
+ clipRect.left -= attrs.surfaceInsets.left;
+ clipRect.top -= attrs.surfaceInsets.top;
+ clipRect.right += attrs.surfaceInsets.right;
+ clipRect.bottom += attrs.surfaceInsets.bottom;
+
+ // If we have an animated clip rect, intersect it with the clip rect.
+ if (mHasClipRect) {
+ // NOTE: We are adding a temporary workaround due to the status bar
+ // not always reporting the correct system decor rect. In such
+ // cases, we take into account the specified content insets as well.
if ((w.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN)
== SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) {
- mTmpClipRect.intersect(mClipRect);
+ // Don't apply the workaround to apps explicitly requesting
+ // fullscreen layout.
+ clipRect.intersect(mClipRect);
} else {
- mTmpClipRect.offset(0, -offsetTop);
- mTmpClipRect.intersect(mClipRect);
- mTmpClipRect.offset(0, offsetTop);
+ final int offsetTop = Math.max(clipRect.top, w.mContentInsets.top);
+ clipRect.offset(0, -offsetTop);
+ clipRect.intersect(mClipRect);
+ clipRect.offset(0, offsetTop);
}
- clipRect = mTmpClipRect;
-
}
+ // The clip rect was generated assuming (0,0) as the window origin,
+ // so we need to translate to match the actual surface coordinates.
+ clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top);
+
if (!clipRect.equals(mLastClipRect)) {
mLastClipRect.set(clipRect);
try {
@@ -1285,10 +1286,10 @@
// Adjust for surface insets.
final LayoutParams attrs = w.getAttrs();
- width += attrs.shadowInsets.left + attrs.shadowInsets.right;
- height += attrs.shadowInsets.top + attrs.shadowInsets.bottom;
- left -= attrs.shadowInsets.left;
- top -= attrs.shadowInsets.top;
+ width += attrs.surfaceInsets.left + attrs.surfaceInsets.right;
+ height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom;
+ left -= attrs.surfaceInsets.left;
+ top -= attrs.surfaceInsets.top;
final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top;
if (surfaceMoved) {
diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
index 1db3c5e..4fe30e6 100644
--- a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
+++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
@@ -23,17 +23,13 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.admin.IDevicePolicyManager;
-import android.content.AbstractRestrictionsProvider;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.IPermissionResponseCallback;
-import android.content.IRestrictionsProvider;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
-import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -127,58 +123,12 @@
enforceCallerMatchesPackage(callingUid, packageName, "Package name does not" +
" match caller ");
// Prepare and broadcast the intent to the provider
- Intent intent = new Intent();
+ Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_PERMISSION);
intent.setComponent(restrictionsProvider);
- new ProviderServiceConnection(intent, null, userHandle) {
- @Override
- public void run() throws RemoteException {
- if (DEBUG) {
- Log.i(LOG_TAG, "calling requestPermission for " + packageName
- + ", type=" + requestType + ", data=" + requestData);
- }
- mRestrictionsProvider.requestPermission(packageName,
- requestType, requestData);
- }
- }.bind();
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public void getPermissionResponse(final String packageName, final String requestId,
- final IPermissionResponseCallback callback) throws RemoteException {
- int callingUid = Binder.getCallingUid();
- int userHandle = UserHandle.getUserId(callingUid);
- if (mDpm != null) {
- long ident = Binder.clearCallingIdentity();
- try {
- ComponentName restrictionsProvider =
- mDpm.getRestrictionsProvider(userHandle);
- // Check if there is a restrictions provider
- if (restrictionsProvider == null) {
- throw new IllegalStateException(
- "Cannot fetch permission without a restrictions provider registered");
- }
- // Check that the packageName matches the caller.
- enforceCallerMatchesPackage(callingUid, packageName, "Package name does not" +
- " match caller ");
- // Prepare and broadcast the intent to the provider
- Intent intent = new Intent();
- intent.setComponent(restrictionsProvider);
- new ProviderServiceConnection(intent, callback.asBinder(), userHandle) {
- @Override
- public void run() throws RemoteException {
- if (DEBUG) {
- Log.i(LOG_TAG, "calling getPermissionResponse for " + packageName
- + ", id=" + requestId);
- }
- Bundle response = mRestrictionsProvider.getPermissionResponse(
- packageName, requestId);
- callback.onResponse(response);
- }
- }.bind();
+ intent.putExtra(RestrictionsManager.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(RestrictionsManager.EXTRA_REQUEST_TYPE, requestType);
+ intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, requestData);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -226,81 +176,5 @@
// Shouldn't happen
}
}
-
- abstract class ProviderServiceConnection
- implements IBinder.DeathRecipient, ServiceConnection {
-
- protected IRestrictionsProvider mRestrictionsProvider;
- private Intent mIntent;
- protected int mUserHandle;
- protected IBinder mResponse;
- private boolean mAbort;
-
- public ProviderServiceConnection(Intent intent, IBinder response, int userHandle) {
- mIntent = intent;
- mResponse = response;
- mUserHandle = userHandle;
- if (mResponse != null) {
- try {
- mResponse.linkToDeath(this, 0 /* flags */);
- } catch (RemoteException re) {
- close();
- }
- }
- }
-
- /** Bind to the RestrictionsProvider process */
- public void bind() {
- if (DEBUG) {
- Log.i(LOG_TAG, "binding to service: " + mIntent);
- }
- mContext.bindServiceAsUser(mIntent, this, Context.BIND_AUTO_CREATE,
- new UserHandle(mUserHandle));
- }
-
- private void close() {
- mAbort = true;
- unbind();
- }
-
- private void unbind() {
- if (DEBUG) {
- Log.i(LOG_TAG, "unbinding from service");
- }
- mContext.unbindService(this);
- }
-
- /** Implement this to call the appropriate method on the service */
- public abstract void run() throws RemoteException;
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) {
- Log.i(LOG_TAG, "connected to " + name);
- }
- mRestrictionsProvider = IRestrictionsProvider.Stub.asInterface(service);
- if (!mAbort) {
- try {
- run();
- } catch (RemoteException re) {
- Log.w("RestrictionsProvider", "Remote exception: " + re);
- }
- }
- close();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) {
- Log.i(LOG_TAG, "disconnected from " + name);
- }
- mRestrictionsProvider = null;
- }
-
- @Override
- public void binderDied() {
- mAbort = true;
- }
- }
}
}
diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java
new file mode 100644
index 0000000..b17f929
--- /dev/null
+++ b/telecomm/java/android/telecomm/Call.java
@@ -0,0 +1,716 @@
+/*
+ * 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 android.telecomm;
+
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents an ongoing phone call that the in-call app should present to the user.
+ */
+public final class Call {
+ /**
+ * The state of a {@code Call} when newly created.
+ */
+ public static final int STATE_NEW = 0;
+
+ /**
+ * The state of an outgoing {@code Call} when dialing the remote number, but not yet connected.
+ */
+ public static final int STATE_DIALING = 1;
+
+ /**
+ * The state of an incoming {@code Call} when ringing locally, but not yet connected.
+ */
+ public static final int STATE_RINGING = 2;
+
+ /**
+ * The state of a {@code Call} when in a holding state.
+ */
+ public static final int STATE_HOLDING = 3;
+
+ /**
+ * The state of a {@code Call} when actively supporting conversation.
+ */
+ public static final int STATE_ACTIVE = 4;
+
+ /**
+ * The state of a {@code Call} when no further voice or other communication is being
+ * transmitted, the remote side has been or will inevitably be informed that the {@code Call}
+ * is no longer active, and the local data transport has or inevitably will release resources
+ * associated with this {@code Call}.
+ */
+ public static final int STATE_DISCONNECTED = 7;
+
+ public static class Details {
+ private final Uri mHandle;
+ private final int mHandlePresentation;
+ private final String mCallerDisplayName;
+ private final int mCallerDisplayNamePresentation;
+ private final PhoneAccount mAccount;
+ private final int mCapabilities;
+ private final int mDisconnectCauseCode;
+ private final String mDisconnectCauseMsg;
+ private final long mConnectTimeMillis;
+ private final GatewayInfo mGatewayInfo;
+ private final int mVideoState;
+
+ /**
+ * @return The handle (e.g., phone number) to which the {@code Call} is currently
+ * connected.
+ */
+ public Uri getHandle() {
+ return mHandle;
+ }
+
+ /**
+ * @return The presentation requirements for the handle. See
+ * {@link android.telecomm.CallPropertyPresentation} for valid values.
+ */
+ public int getHandlePresentation() {
+ return mHandlePresentation;
+ }
+
+ /**
+ * @return The display name for the caller.
+ */
+ public String getCallerDisplayName() {
+ return mCallerDisplayName;
+ }
+
+ /**
+ * @return The presentation requirements for the caller display name. See
+ * {@link android.telecomm.CallPropertyPresentation} for valid values.
+ */
+ public int getCallerDisplayNamePresentation() {
+ return mCallerDisplayNamePresentation;
+ }
+
+ /**
+ * @return The {@code PhoneAccount} whereby the {@code Call} is currently being routed.
+ */
+ public PhoneAccount getAccount() {
+ return mAccount;
+ }
+
+ /**
+ * @return A bitmask of the capabilities of the {@code Call}, as defined in
+ * {@link CallCapabilities}.
+ */
+ public int getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * @return For a {@link #STATE_DISCONNECTED} {@code Call}, the disconnect cause expressed
+ * as a code chosen from among those declared in {@link DisconnectCause}.
+ */
+ public int getDisconnectCauseCode() {
+ return mDisconnectCauseCode;
+ }
+
+ /**
+ * @return For a {@link #STATE_DISCONNECTED} {@code Call}, an optional reason for
+ * disconnection expressed as a free text message.
+ */
+ public String getDisconnectCauseMsg() {
+ return mDisconnectCauseMsg;
+ }
+
+ /**
+ * @return The time the {@code Call} has been connected. This information is updated
+ * periodically, but user interfaces should not rely on this to display any "call time
+ * clock".
+ */
+ public long getConnectTimeMillis() {
+ return mConnectTimeMillis;
+ }
+
+ /**
+ * @return Information about any calling gateway the {@code Call} may be using.
+ */
+ public GatewayInfo getGatewayInfo() {
+ return mGatewayInfo;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Details) {
+ Details d = (Details) o;
+ return
+ Objects.equals(mHandle, d.mHandle) &&
+ Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
+ Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
+ Objects.equals(mCallerDisplayNamePresentation,
+ d.mCallerDisplayNamePresentation) &&
+ Objects.equals(mAccount, d.mAccount) &&
+ Objects.equals(mCapabilities, d.mCapabilities) &&
+ Objects.equals(mDisconnectCauseCode, d.mDisconnectCauseCode) &&
+ Objects.equals(mDisconnectCauseMsg, d.mDisconnectCauseMsg) &&
+ Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) &&
+ Objects.equals(mGatewayInfo, d.mGatewayInfo) &&
+ Objects.equals(mVideoState, d.mVideoState);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return
+ Objects.hashCode(mHandle) +
+ Objects.hashCode(mHandlePresentation) +
+ Objects.hashCode(mCallerDisplayName) +
+ Objects.hashCode(mCallerDisplayNamePresentation) +
+ Objects.hashCode(mAccount) +
+ Objects.hashCode(mCapabilities) +
+ Objects.hashCode(mDisconnectCauseCode) +
+ Objects.hashCode(mDisconnectCauseMsg) +
+ Objects.hashCode(mConnectTimeMillis) +
+ Objects.hashCode(mGatewayInfo) +
+ Objects.hashCode(mVideoState);
+ }
+
+ /** {@hide} */
+ public Details(
+ Uri handle,
+ int handlePresentation,
+ String callerDisplayName,
+ int callerDisplayNamePresentation,
+ PhoneAccount account,
+ int capabilities,
+ int disconnectCauseCode,
+ String disconnectCauseMsg,
+ long connectTimeMillis,
+ GatewayInfo gatewayInfo,
+ int videoState) {
+ mHandle = handle;
+ mHandlePresentation = handlePresentation;
+ mCallerDisplayName = callerDisplayName;
+ mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+ mAccount = account;
+ mCapabilities = capabilities;
+ mDisconnectCauseCode = disconnectCauseCode;
+ mDisconnectCauseMsg = disconnectCauseMsg;
+ mConnectTimeMillis = connectTimeMillis;
+ mGatewayInfo = gatewayInfo;
+ mVideoState = videoState;
+ }
+ }
+
+ public static abstract class Listener {
+ /**
+ * Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
+ *
+ * TODO(ihab): Provide previous state also?
+ *
+ * @param call The {@code Call} invoking this method.
+ * @param state The new state of the {@code Call}.
+ */
+ public void onStateChanged(Call call, int state) {}
+
+ /**
+ * Invoked when the parent of this {@code Call} has changed. See {@link #getParent()}.
+ *
+ * @param call The {@code Call} invoking this method.
+ * @param parent The new parent of the {@code Call}.
+ */
+ public void onParentChanged(Call call, Call parent) {}
+
+ /**
+ * Invoked when the children of this {@code Call} have changed. See {@link #getChildren()}.
+ *
+ * @param call The {@code Call} invoking this method.
+ * @param children The new children of the {@code Call}.
+ */
+ public void onChildrenChanged(Call call, List<Call> children) {}
+
+ /**
+ * Invoked when the details of this {@code Call} have changed. See {@link #getDetails()}.
+ *
+ * @param call The {@code Call} invoking this method.
+ * @param details A {@code Details} object describing the {@code Call}.
+ */
+ public void onDetailsChanged(Call call, Details details) {}
+
+ /**
+ * Invoked when the text messages that can be used as responses to the incoming
+ * {@code Call} are loaded from the relevant database.
+ * See {@link #getCannedTextResponses()}.
+ *
+ * @param call The {@code Call} invoking this method.
+ * @param cannedTextResponses The text messages useable as responses.
+ */
+ public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {}
+
+ /**
+ * Invoked when the outgoing {@code Call} has finished dialing but is sending DTMF signals
+ * that were embedded into the outgoing number.
+ *
+ * @param call The {@code Call} invoking this method.
+ * @param remainingPostDialSequence The post-dial characters that remain to be sent.
+ */
+ public void onPostDial(Call call, String remainingPostDialSequence) {}
+
+ /**
+ * Invoked when the post-dial sequence in the outgoing {@code Call} has reached a pause
+ * character. This causes the post-dial signals to stop pending user confirmation. An
+ * implementation should present this choice to the user and invoke
+ * {@link #postDialContinue(boolean)} when the user makes the choice.
+ *
+ * @param call The {@code Call} invoking this method.
+ * @param remainingPostDialSequence The post-dial characters that remain to be sent.
+ */
+ public void onPostDialWait(Call call, String remainingPostDialSequence) {}
+
+ /**
+ * Invoked when the {@code RemoteCallVideoProvider} of the {@code Call} has changed.
+ *
+ * @param call The {@code Call} invoking this method.
+ * @param callVideoProvider The {@code RemoteCallVideoProvider} associated with the
+ * {@code Call}.
+ */
+
+ public void onCallVideoProviderChanged(Call call,
+ RemoteCallVideoProvider callVideoProvider) {}
+
+ /**
+ * Invoked when the {@code Call} is destroyed. Clients should refrain from cleaning
+ * up their UI for the {@code Call} in response to state transitions. Specifically,
+ * clients should not assume that a {@link #onStateChanged(Call, int)} with a state of
+ * {@link #STATE_DISCONNECTED} is the final notification the {@code Call} will send. Rather,
+ * clients should wait for this method to be invoked.
+ *
+ * @param call The {@code Call} being destroyed.
+ */
+ public void onCallDestroyed(Call call) {}
+ }
+
+ private final Phone mPhone;
+ private final String mTelecommCallId;
+ private final InCallAdapter mInCallAdapter;
+ private Call mParent = null;
+ private int mState;
+ private final List<Call> mChildren = new ArrayList<>();
+ private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren);
+ private List<String> mCannedTextResponses = null;
+ private String mRemainingPostDialSequence;
+ private RemoteCallVideoProvider mCallVideoProvider;
+ private Details mDetails;
+ private final List<Listener> mListeners = new ArrayList<>();
+
+ /**
+ * Obtains the post-dial sequence remaining to be emitted by this {@code Call}, if any.
+ *
+ * @return The remaining post-dial sequence, or {@code null} if there is no post-dial sequence
+ * remaining or this {@code Call} is not in a post-dial state.
+ */
+ public String getRemainingPostDialSequence() {
+ return mRemainingPostDialSequence;
+ }
+
+ /**
+ * Instructs this {@link #STATE_RINGING} {@code Call} to answer.
+ */
+ public void answer() {
+ mInCallAdapter.answerCall(mTelecommCallId);
+ }
+
+ /**
+ * Instructs this {@link #STATE_RINGING} {@code Call} to reject.
+ *
+ * @param rejectWithMessage Whether to reject with a text message.
+ * @param textMessage An optional text message with which to respond.
+ */
+ public void reject(boolean rejectWithMessage, String textMessage) {
+ mInCallAdapter.rejectCall(mTelecommCallId, rejectWithMessage, textMessage);
+ }
+
+ /**
+ * Instructs this {@code Call} to disconnect.
+ */
+ public void disconnect() {
+ mInCallAdapter.disconnectCall(mTelecommCallId);
+ }
+
+ /**
+ * Instructs this {@code Call} to go on hold.
+ */
+ public void hold() {
+ mInCallAdapter.holdCall(mTelecommCallId);
+ }
+
+ /**
+ * Instructs this {@link #STATE_HOLDING} call to release from hold.
+ */
+ public void unhold() {
+ mInCallAdapter.unholdCall(mTelecommCallId);
+ }
+
+ /**
+ * Instructs this {@code Call} to play a dual-tone multi-frequency signaling (DTMF) tone.
+ *
+ * Any other currently playing DTMF tone in the specified call is immediately stopped.
+ *
+ * @param digit A character representing the DTMF digit for which to play the tone. This
+ * value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}.
+ */
+ public void playDtmfTone(char digit) {
+ mInCallAdapter.playDtmfTone(mTelecommCallId, digit);
+ }
+
+ /**
+ * Instructs this {@code Call} to stop any dual-tone multi-frequency signaling (DTMF) tone
+ * currently playing.
+ *
+ * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is
+ * currently playing, this method will do nothing.
+ */
+ public void stopDtmfTone() {
+ mInCallAdapter.stopDtmfTone(mTelecommCallId);
+ }
+
+ /**
+ * Instructs this {@code Call} to continue playing a post-dial DTMF string.
+ *
+ * A post-dial DTMF string is a string of digits entered after a phone number, when dialed,
+ * that are immediately sent as DTMF tones to the recipient as soon as the connection is made.
+ * While these tones are playing, this {@code Call} will notify listeners via
+ * {@link Listener#onPostDial(Call, String)}.
+ *
+ * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_PAUSE} symbol, this
+ * {@code Call} will temporarily pause playing the tones for a pre-defined period of time.
+ *
+ * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_WAIT} symbol, this
+ * {@code Call} will pause playing the tones and notify listeners via
+ * {@link Listener#onPostDialWait(Call, String)}. At this point, the in-call app
+ * should display to the user an indication of this state and an affordance to continue
+ * the postdial sequence. When the user decides to continue the postdial sequence, the in-call
+ * app should invoke the {@link #postDialContinue(boolean)} method.
+ *
+ * @param proceed Whether or not to continue with the post-dial sequence.
+ */
+ public void postDialContinue(boolean proceed) {
+ mInCallAdapter.postDialContinue(mTelecommCallId, proceed);
+ }
+
+ /**
+ * Notifies this {@code Call} that the phone account user interface element was touched.
+ *
+ * TODO(ihab): Figure out if and how we can generalize this
+ */
+ public void phoneAccountClicked() {
+ mInCallAdapter.phoneAccountClicked(mTelecommCallId);
+ }
+
+ /**
+ * Instructs this {@code Call} to enter a conference.
+ */
+ public void conference() {
+ mInCallAdapter.conference(mTelecommCallId);
+ }
+
+ /**
+ * Instructs this {@code Call} to split from any conference call with which it may be
+ * connected.
+ */
+ public void splitFromConference() {
+ mInCallAdapter.splitFromConference(mTelecommCallId);
+ }
+
+ /**
+ * Instructs this {@code Call} to swap itself with an existing background call, if one
+ * such call exists.
+ */
+ public void swapWithBackgroundCall() {
+ mInCallAdapter.swapWithBackgroundCall(mTelecommCallId);
+ }
+
+ /**
+ * Obtains the parent of this {@code Call} in a conference, if any.
+ *
+ * @return The parent {@code Call}, or {@code null} if this {@code Call} is not a
+ * child of any conference {@code Call}s.
+ */
+ public Call getParent() {
+ return mParent;
+ }
+
+ /**
+ * Obtains the children of this conference {@code Call}, if any.
+ *
+ * @return The children of this {@code Call} if this {@code Call} is a conference, or an empty
+ * {@code List} otherwise.
+ */
+ public List<Call> getChildren() {
+ return mUnmodifiableChildren;
+ }
+
+ /**
+ * Obtains the state of this {@code Call}.
+ *
+ * @return A state value, chosen from the {@code STATE_*} constants.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Obtains a list of canned, pre-configured message responses to present to the user as
+ * ways of rejecting this {@code Call} using via a text message.
+ *
+ * @see #reject(boolean, String)
+ *
+ * @return A list of canned text message responses.
+ */
+ public List<String> getCannedTextResponses() {
+ return mCannedTextResponses;
+ }
+
+ /**
+ * Obtains an object that can be used to display video from this {@code Call}.
+ *
+ * @return An {@code ICallVideoProvider}.
+ */
+ public RemoteCallVideoProvider getCallVideoProvider() {
+ return mCallVideoProvider;
+ }
+
+ /**
+ * Obtains an object containing call details.
+ *
+ * @return A {@link Details} object. Depending on the state of the {@code Call}, the
+ * result may be {@code null}.
+ */
+ public Details getDetails() {
+ return mDetails;
+ }
+
+ /**
+ * Adds a listener to this {@code Call}.
+ *
+ * @param listener A {@code Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener from this {@code Call}.
+ *
+ * @param listener A {@code Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ /** {@hide} */
+ Call(Phone phone, String telecommCallId, InCallAdapter inCallAdapter) {
+ mPhone = phone;
+ mTelecommCallId = telecommCallId;
+ mInCallAdapter = inCallAdapter;
+ mState = STATE_NEW;
+ }
+
+ /** {@hide} */
+ final String internalGetCallId() {
+ return mTelecommCallId;
+ }
+
+ /** {@hide} */
+ final void internalUpdate(InCallCall inCallCall) {
+ // First, we update the internal state as far as possible before firing any updates.
+
+ Details details = new Details(
+ inCallCall.getHandle(),
+ inCallCall.getHandlePresentation(),
+ inCallCall.getCallerDisplayName(),
+ inCallCall.getCallerDisplayNamePresentation(),
+ inCallCall.getAccount(),
+ inCallCall.getCapabilities(),
+ inCallCall.getDisconnectCauseCode(),
+ inCallCall.getDisconnectCauseMsg(),
+ inCallCall.getConnectTimeMillis(),
+ inCallCall.getGatewayInfo(),
+ inCallCall.getVideoState());
+ boolean detailsChanged = !Objects.equals(mDetails, details);
+ if (detailsChanged) {
+ mDetails = details;
+ }
+
+ boolean cannedTextResponsesChanged = false;
+ if (mCannedTextResponses == null && inCallCall.getCannedSmsResponses() != null
+ && !inCallCall.getCannedSmsResponses().isEmpty()) {
+ mCannedTextResponses = Collections.unmodifiableList(inCallCall.getCannedSmsResponses());
+ }
+
+ boolean callVideoProviderChanged = false;
+ try {
+ callVideoProviderChanged =
+ !Objects.equals(mCallVideoProvider, inCallCall.getCallVideoProvider());
+ if (callVideoProviderChanged) {
+ mCallVideoProvider = inCallCall.getCallVideoProvider();
+ }
+ } catch (RemoteException e) {
+ }
+
+ int state = stateFromInCallCallState(inCallCall.getState());
+ boolean stateChanged = mState != state;
+ if (stateChanged) {
+ mState = state;
+ }
+
+ if (inCallCall.getParentCallId() != null) {
+ mParent = mPhone.internalGetCallByTelecommId(inCallCall.getParentCallId());
+ }
+
+ mChildren.clear();
+ if (inCallCall.getChildCallIds() != null) {
+ for (int i = 0; i < inCallCall.getChildCallIds().size(); i++) {
+ mChildren.add(mPhone.internalGetCallByTelecommId(
+ inCallCall.getChildCallIds().get(i)));
+ }
+ }
+
+ // Now we fire updates, ensuring that any client who listens to any of these notifications
+ // gets the most up-to-date state.
+
+ if (stateChanged) {
+ fireStateChanged(mState);
+ }
+ if (detailsChanged) {
+ fireDetailsChanged(mDetails);
+ }
+ if (cannedTextResponsesChanged) {
+ fireCannedTextResponsesLoaded(mCannedTextResponses);
+ }
+ if (callVideoProviderChanged) {
+ fireCallVideoProviderChanged(mCallVideoProvider);
+ }
+
+ // If we have transitioned to DISCONNECTED, that means we need to notify clients and
+ // remove ourselves from the Phone. Note that we do this after completing all state updates
+ // so a client can cleanly transition all their UI to the state appropriate for a
+ // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list.
+ if (mState == STATE_DISCONNECTED) {
+ fireCallDestroyed();
+ mPhone.internalRemoveCall(this);
+ }
+ }
+
+ /** {@hide} */
+ final void internalSetPostDial(String remaining) {
+ mRemainingPostDialSequence = remaining;
+ firePostDial(mRemainingPostDialSequence);
+ }
+
+ /** {@hide} */
+ final void internalSetPostDialWait(String remaining) {
+ mRemainingPostDialSequence = remaining;
+ firePostDialWait(mRemainingPostDialSequence);
+ }
+
+ private void fireStateChanged(int newState) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onStateChanged(this, newState);
+ }
+ }
+
+ private void fireParentChanged(Call newParent) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onParentChanged(this, newParent);
+ }
+ }
+
+ private void fireChildrenChanged(List<Call> children) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onChildrenChanged(this, children);
+ }
+ }
+
+ private void fireDetailsChanged(Details details) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onDetailsChanged(this, details);
+ }
+ }
+
+ private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onCannedTextResponsesLoaded(this, cannedTextResponses);
+ }
+ }
+
+ private void fireCallVideoProviderChanged(RemoteCallVideoProvider callVideoProvider) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onCallVideoProviderChanged(this, callVideoProvider);
+ }
+ }
+
+ private void firePostDial(String remainingPostDialSequence) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onPostDial(this, remainingPostDialSequence);
+ }
+ }
+
+ private void firePostDialWait(String remainingPostDialSequence) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onPostDialWait(this, remainingPostDialSequence);
+ }
+ }
+
+ private void fireCallDestroyed() {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onCallDestroyed(this);
+ }
+ }
+
+ private int stateFromInCallCallState(CallState inCallCallState) {
+ switch (inCallCallState) {
+ case NEW:
+ return STATE_NEW;
+ case DIALING:
+ return STATE_DIALING;
+ case RINGING:
+ return STATE_RINGING;
+ case ACTIVE:
+ return STATE_ACTIVE;
+ case ON_HOLD:
+ return STATE_HOLDING;
+ case DISCONNECTED:
+ return STATE_DISCONNECTED;
+ case ABORTED:
+ return STATE_DISCONNECTED;
+ default:
+ Log.wtf(this, "Unrecognized CallState %s", inCallCallState);
+ return STATE_NEW;
+ }
+ }
+}
diff --git a/telecomm/java/android/telecomm/CallPropertyPresentation.java b/telecomm/java/android/telecomm/CallPropertyPresentation.java
index 350980c..319e565 100644
--- a/telecomm/java/android/telecomm/CallPropertyPresentation.java
+++ b/telecomm/java/android/telecomm/CallPropertyPresentation.java
@@ -19,14 +19,14 @@
/** Defines how numbers and names are displayed in caller id. */
public class CallPropertyPresentation {
/** Property is displayed normally. */
- public static final int ALLOWED = 0;
+ public static final int ALLOWED = 1;
/** Property was blocked. */
- public static final int RESTRICTED = 1;
+ public static final int RESTRICTED = 2;
/** Presentation was not specified or is unknown. */
- public static final int UNKNOWN = 2;
+ public static final int UNKNOWN = 3;
/** Property should be displayed as a pay phone. */
- public static final int PAYPHONE = 3;
+ public static final int PAYPHONE = 4;
}
diff --git a/telecomm/java/android/telecomm/CallServiceDescriptor.java b/telecomm/java/android/telecomm/CallServiceDescriptor.java
deleted file mode 100644
index 5ae07d3..0000000
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright 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 android.telecomm;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-
-import java.util.Locale;
-import java.util.UUID;
-
-/**
- * An immutable object containing information about a given {@link ConnectionService}. Instances are
- * created using the enclosed {@link Builder}.
- */
-public final class CallServiceDescriptor implements Parcelable {
- private static final String TAG = CallServiceDescriptor.class.getSimpleName();
-
- /**
- * A placeholder value indicating an invalid network type.
- * @hide
- */
- private static final int FLAG_INVALID = 0;
-
- /**
- * Indicates that the device must be connected to a Wi-Fi network in order for the backing
- * {@link ConnectionService} to be used.
- */
- public static final int FLAG_WIFI = 0x01;
-
- /**
- * Indicates that the device must be connected to a cellular PSTN network in order for the
- * backing {@link ConnectionService} to be used.
- */
- public static final int FLAG_PSTN = 0x02;
-
- /**
- * Indicates that the device must be connected to a cellular data network in order for the
- * backing {@link ConnectionService} to be used.
- */
- public static final int FLAG_MOBILE = 0x04;
-
- /**
- * Represents all of the defined FLAG_ constants so validity can be easily checked.
- * @hide
- */
- public static final int FLAG_ALL = FLAG_WIFI | FLAG_PSTN | FLAG_MOBILE;
-
- /**
- * A unique ID used to identify a given instance.
- */
- private final String mConnectionServiceId;
-
- /**
- * The {@link ComponentName} of the {@link ConnectionService} implementation which this is
- * describing.
- */
- private final ComponentName mComponentName;
-
- /**
- * The type of connection that the {@link ConnectionService} requires; will be one of the FLAG_*
- * constants defined in this class.
- */
- private final int mNetworkType;
-
- private CallServiceDescriptor(
- String connectionServiceId,
- ComponentName componentName,
- int networkType) {
-
- mConnectionServiceId = connectionServiceId;
- mComponentName = componentName;
- mNetworkType = networkType;
- }
-
- /**
- * @return The ID used to identify this {@link ConnectionService}.
- */
- public String getConnectionServiceId() {
- return mConnectionServiceId;
- }
-
- /**
- * @return The {@link ComponentName} of the {@link ConnectionService}.
- */
- public ComponentName getServiceComponent() {
- return mComponentName;
- }
-
- /**
- * @return The network type required by the {@link ConnectionService} to place a call.
- */
- public int getNetworkType() {
- return mNetworkType;
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof CallServiceDescriptor)) {
- return false;
- }
- CallServiceDescriptor descriptor = (CallServiceDescriptor) obj;
- return mConnectionServiceId.equals(descriptor.mConnectionServiceId) &&
- mComponentName.equals(descriptor.mComponentName) &&
- mNetworkType == descriptor.mNetworkType;
- }
-
- @Override
- public String toString() {
- return String.format(Locale.US, "[%s, component: %s]",
- CallServiceDescriptor.class.getSimpleName(),
- mComponentName == null ? "null" : mComponentName.flattenToShortString());
- }
-
- /**
- * @param context {@link Context} to use for the construction of the {@link Builder}.
- * @return A new {@link Builder} instance.
- */
- public static Builder newBuilder(Context context) {
- return new Builder(context);
- }
-
- /**
- * Creates {@link CallServiceDescriptor} instances. Builders should be created with the
- * {@link CallServiceDescriptor#newBuilder(Context)} method.
- */
- public static class Builder {
- /** The {@link Context} to use to verify {@link ComponentName} ownership. */
- private Context mContext;
-
- /** The {@link ComponentName} pointing to the backing {@link ConnectionService}. */
- private ComponentName mComponentName;
-
- /** The required network type that the {@link ConnectionService} needs. */
- private int mNetworkType = FLAG_INVALID;
-
- private Builder(Context context) {
- mContext = context;
- }
-
- /**
- * Set which {@link ConnectionService} this {@link CallServiceDescriptor} is describing.
- *
- * @param serviceClass The {@link ConnectionService} class
- * @return This {@link Builder} for method chaining.
- */
- public Builder setConnectionService(Class<? extends ConnectionService> serviceClass) {
- mComponentName = new ComponentName(mContext, serviceClass);
- return this;
- }
-
- /**
- * Which network type the backing {@link ConnectionService} requires. This must be one of
- * the {@link CallServiceDescriptor}.TYPE_* fields.
- *
- * @param networkType Which network type the backing {@link ConnectionService} requires.
- * @return This {@link Builder} for method chaining.
- */
- public Builder setNetworkType(int networkType) {
- mNetworkType = networkType;
- return this;
- }
-
- /**
- * @return A constructed {@link CallServiceDescriptor} object.
- */
- public CallServiceDescriptor build() {
- // STOPSHIP: Verify validity of ComponentName (permissions, intents, etc)
-
- // Make sure that they passed in a valid network flag combination
- if (mNetworkType == FLAG_INVALID || ((mNetworkType & FLAG_ALL) == 0)) {
-
- Log.wtf(TAG, "Invalid network type for " + mComponentName);
- // Revert them back to TYPE_INVALID so it won't be considered.
- mNetworkType = FLAG_INVALID;
- }
-
- // TODO: Should we use a sha1 of the ComponentName? Would prevent duplicates.
- return new CallServiceDescriptor(
- UUID.randomUUID().toString(), mComponentName, mNetworkType);
- }
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mConnectionServiceId);
- dest.writeParcelable(mComponentName, 0);
- dest.writeInt(mNetworkType);
- }
-
- public static final Creator<CallServiceDescriptor> CREATOR =
- new Creator<CallServiceDescriptor>() {
- @Override
- public CallServiceDescriptor createFromParcel(Parcel source) {
- String id = source.readString();
- ComponentName componentName = source.readParcelable(
- CallServiceDescriptor.class.getClassLoader());
- int networkType = source.readInt();
-
- return new CallServiceDescriptor(id, componentName, networkType);
- }
-
- @Override
- public CallServiceDescriptor[] newArray(int size) {
- return new CallServiceDescriptor[size];
- }
- };
-}
diff --git a/telecomm/java/android/telecomm/CallServiceLookupResponse.java b/telecomm/java/android/telecomm/CallServiceLookupResponse.java
deleted file mode 100644
index dd35a24..0000000
--- a/telecomm/java/android/telecomm/CallServiceLookupResponse.java
+++ /dev/null
@@ -1,51 +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 android.telecomm;
-
-import android.os.RemoteException;
-
-import com.android.internal.telecomm.ICallServiceLookupResponse;
-
-import java.util.List;
-
-/**
- * Used by {@link CallServiceProvider} to return a list of {@link CallServiceDescriptor}s.
- */
-public final class CallServiceLookupResponse {
- private final ICallServiceLookupResponse mResponse;
-
- /**
- * {@hide}
- */
- public CallServiceLookupResponse(ICallServiceLookupResponse response) {
- mResponse = response;
- }
-
- /**
- * Passes the sorted list of preferred {@link CallServiceDescriptor}s back to Telecomm. Used
- * in the context of attempting to place a pending outgoing call.
- *
- * @param callServiceDescriptors The set of call-service descriptors from
- * {@link CallServiceProvider}.
- */
- public void setCallServiceDescriptors(List<CallServiceDescriptor> callServiceDescriptors) {
- try {
- mResponse.setCallServiceDescriptors(callServiceDescriptors);
- } catch (RemoteException e) {
- }
- }
-}
diff --git a/telecomm/java/android/telecomm/CallServiceProvider.java b/telecomm/java/android/telecomm/CallServiceProvider.java
deleted file mode 100644
index c50334a..0000000
--- a/telecomm/java/android/telecomm/CallServiceProvider.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telecomm;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-
-import com.android.internal.telecomm.ICallServiceLookupResponse;
-import com.android.internal.telecomm.ICallServiceProvider;
-
-/**
- * Base implementation of a call service provider which extends {@link Service}. This class
- * should be extended by an app that wants to supply phone calls to be handled and managed by
- * the device's in-call interface. All method-calls from the framework to the call service provider
- * are passed through to the main thread for before executing the overriden methods of
- * CallServiceProvider.
- *
- * TODO(santoscordon): Improve paragraph above once the final design is in place. Needs more
- * about how this can be used.
- */
-public abstract class CallServiceProvider extends Service {
-
- /**
- * Default Handler used to consolidate binder method calls onto a single thread.
- */
- private final class CallServiceProviderMessageHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_LOOKUP_CALL_SERVICES:
- CallServiceLookupResponse response =
- new CallServiceLookupResponse((ICallServiceLookupResponse) msg.obj);
- lookupCallServices(response);
- break;
- }
- }
- }
-
- /**
- * Default ICallServiceProvider implementation provided to CallsManager via {@link #onBind}.
- */
- private final class CallServiceProviderWrapper extends ICallServiceProvider.Stub {
- /** {@inheritDoc} */
- @Override
- public void lookupCallServices(ICallServiceLookupResponse callServiceLookupResponse) {
- Message message = mMessageHandler.obtainMessage(
- MSG_LOOKUP_CALL_SERVICES, callServiceLookupResponse);
- message.sendToTarget();
- }
- }
-
- // Only used internally by this class.
- // Binder method calls on this service can occur on multiple threads. These messages are used
- // in conjunction with {@link #mMessageHandler} to ensure that all callbacks are handled on a
- // single thread. Keeping it on a single thread allows CallService implementations to avoid
- // needing multi-threaded code in their own callback routines.
- private static final int MSG_LOOKUP_CALL_SERVICES = 1;
-
- /**
- * Message handler for consolidating binder callbacks onto a single thread.
- * See {@link CallServiceProviderMessageHandler}.
- */
- private final CallServiceProviderMessageHandler mMessageHandler;
-
- /**
- * Default binder implementation of {@link ICallServiceProvider} interface.
- */
- private final CallServiceProviderWrapper mBinder;
-
- /**
- * Protected constructor called only by subclasses creates the binder interface and
- * single-threaded message handler.
- */
- protected CallServiceProvider() {
- mMessageHandler = new CallServiceProviderMessageHandler();
- mBinder = new CallServiceProviderWrapper();
- }
-
- /** {@inheritDoc} */
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- /**
- * Initiates the process to retrieve the list of {@link CallServiceDescriptor}s implemented by
- * this provider.
- *
- * @param response The response object through which the list of call services is sent.
- */
- public abstract void lookupCallServices(CallServiceLookupResponse response);
-}
diff --git a/telecomm/java/android/telecomm/CallState.java b/telecomm/java/android/telecomm/CallState.java
index 152c202..a464da5 100644
--- a/telecomm/java/android/telecomm/CallState.java
+++ b/telecomm/java/android/telecomm/CallState.java
@@ -48,22 +48,6 @@
RINGING,
/**
- * Indicates that the call is active but in a "post-dial" state where Telecomm is now sending
- * some dual-tone multi-frequency signaling (DTMF) tones appended to the dialed number. Normal
- * transitions are to {@link #POST_DIAL_WAIT} when the post-dial string requires user
- * confirmation to proceed, {@link #ACTIVE} when the post-dial tones are completed, or
- * {@link #DISCONNECTED}.
- */
- POST_DIAL,
-
- /**
- * Indicates that the call was in the {@link #POST_DIAL} state but is now waiting for user
- * confirmation before the remaining digits can be sent. Normal transitions are to
- * {@link #POST_DIAL} when the user asks Telecomm to proceed with the post-dial sequence.
- */
- POST_DIAL_WAIT,
-
- /**
* Indicates that a call is currently connected to another party and a communication channel is
* open between them. The normal transition to this state is by the user answering a
* {@link #DIALING} call or a {@link #RINGING} call being answered by the other party.
diff --git a/telecomm/java/android/telecomm/CallVideoClient.java b/telecomm/java/android/telecomm/CallVideoClient.java
index 76b28fa..fb970dc 100644
--- a/telecomm/java/android/telecomm/CallVideoClient.java
+++ b/telecomm/java/android/telecomm/CallVideoClient.java
@@ -241,6 +241,7 @@
*
* @param callCameraCapabilities The changed camera capabilities.
*/
- public abstract void onHandleCameraCapabilitiesChange(CallCameraCapabilities callCameraCapabilities);
+ public abstract void onHandleCameraCapabilitiesChange(
+ CallCameraCapabilities callCameraCapabilities);
}
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index 0db9e29..5888d6a 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -30,11 +30,11 @@
// TODO: Token to limit recursive invocations
// TODO: Consider upgrading "mHandle" to ordered list of handles, indicating a set of phone
// numbers that would satisfy the client's needs, in order of preference
+ private final PhoneAccount mAccount;
private final String mCallId;
private final Uri mHandle;
private final int mHandlePresentation;
private final Bundle mExtras;
- private final PhoneAccount mAccount;
private final int mVideoState;
/**
@@ -61,6 +61,15 @@
mVideoState = videoState;
}
+ private ConnectionRequest(Parcel in) {
+ mAccount = in.readParcelable(getClass().getClassLoader());
+ mCallId = in.readString();
+ mHandle = in.readParcelable(getClass().getClassLoader());
+ mHandlePresentation = in.readInt();
+ mExtras = in.readParcelable(getClass().getClassLoader());
+ mVideoState = in.readInt();
+ }
+
/**
* The account which should be used to place the call.
*/
@@ -109,26 +118,17 @@
mExtras == null ? "" : mExtras);
}
- public static final Parcelable.Creator<ConnectionRequest> CREATOR =
- new Parcelable.Creator<ConnectionRequest> () {
- @Override
- public ConnectionRequest createFromParcel(Parcel source) {
- PhoneAccount account = (PhoneAccount) source.readParcelable(
- getClass().getClassLoader());
- String callId = source.readString();
- Uri handle = (Uri) source.readParcelable(getClass().getClassLoader());
- int presentation = source.readInt();
- Bundle extras = (Bundle) source.readParcelable(getClass().getClassLoader());
- int videoState = source.readInt();
- return new ConnectionRequest(
- account, callId, handle, presentation, extras, videoState);
- }
+ public static final Creator<ConnectionRequest> CREATOR = new Creator<ConnectionRequest> () {
+ @Override
+ public ConnectionRequest createFromParcel(Parcel source) {
+ return new ConnectionRequest(source);
+ }
- @Override
- public ConnectionRequest[] newArray(int size) {
- return new ConnectionRequest[size];
- }
- };
+ @Override
+ public ConnectionRequest[] newArray(int size) {
+ return new ConnectionRequest[size];
+ }
+ };
/**
* {@inheritDoc}
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 1966081..178cee8 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -47,22 +47,21 @@
private static final Connection NULL_CONNECTION = new Connection() {};
private static final int MSG_ADD_CALL_SERVICE_ADAPTER = 1;
- private static final int MSG_CALL = 2;
+ private static final int MSG_CREATE_CONNECTION = 2;
private static final int MSG_ABORT = 3;
- private static final int MSG_CREATE_INCOMING_CALL = 4;
- private static final int MSG_ANSWER = 5;
- private static final int MSG_REJECT = 6;
- private static final int MSG_DISCONNECT = 7;
- private static final int MSG_HOLD = 8;
- private static final int MSG_UNHOLD = 9;
- private static final int MSG_ON_AUDIO_STATE_CHANGED = 10;
- private static final int MSG_PLAY_DTMF_TONE = 11;
- private static final int MSG_STOP_DTMF_TONE = 12;
- private static final int MSG_CONFERENCE = 13;
- private static final int MSG_SPLIT_FROM_CONFERENCE = 14;
- private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 15;
- private static final int MSG_ON_POST_DIAL_CONTINUE = 16;
- private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 17;
+ private static final int MSG_ANSWER = 4;
+ private static final int MSG_REJECT = 5;
+ private static final int MSG_DISCONNECT = 6;
+ private static final int MSG_HOLD = 7;
+ private static final int MSG_UNHOLD = 8;
+ private static final int MSG_ON_AUDIO_STATE_CHANGED = 9;
+ private static final int MSG_PLAY_DTMF_TONE = 10;
+ private static final int MSG_STOP_DTMF_TONE = 11;
+ private static final int MSG_CONFERENCE = 12;
+ private static final int MSG_SPLIT_FROM_CONFERENCE = 13;
+ private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 14;
+ private static final int MSG_ON_POST_DIAL_CONTINUE = 15;
+ private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 16;
private final Map<String, Connection> mConnectionById = new HashMap<>();
private final Map<Connection, String> mIdByConnection = new HashMap<>();
@@ -74,11 +73,11 @@
private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter();
/**
- * A callback for providing the resuilt of creating a connection.
+ * A callback for providing the result of creating a connection.
*/
- public interface OutgoingCallResponse<CONNECTION> {
+ public interface CreateConnectionResponse<CONNECTION> {
/**
- * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
+ * Tells Telecomm that an attempt to create the connection succeeded.
*
* @param request The original request.
* @param connection The connection.
@@ -86,7 +85,8 @@
void onSuccess(ConnectionRequest request, CONNECTION connection);
/**
- * Tells Telecomm that an attempt to place the specified outgoing call failed.
+ * Tells Telecomm that an attempt to create the connection failed. Telecomm will try a
+ * different service until a service cancels the process or completes it successfully.
*
* @param request The original request.
* @param code An integer code indicating the reason for failure.
@@ -95,7 +95,8 @@
void onFailure(ConnectionRequest request, int code, String msg);
/**
- * Tells Telecomm to cancel the call.
+ * Tells Telecomm to cancel creating the connection. Telecomm will stop trying to create
+ * the connection an no more services will be tried.
*
* @param request The original request.
*/
@@ -109,8 +110,9 @@
}
@Override
- public void call(ConnectionRequest request) {
- mHandler.obtainMessage(MSG_CALL, request).sendToTarget();
+ public void createConnection(ConnectionRequest request, boolean isIncoming) {
+ mHandler.obtainMessage(
+ MSG_CREATE_CONNECTION, isIncoming ? 1 : 0, 0, request).sendToTarget();
}
@Override
@@ -119,11 +121,6 @@
}
@Override
- public void createIncomingCall(ConnectionRequest request) {
- mHandler.obtainMessage(MSG_CREATE_INCOMING_CALL, request).sendToTarget();
- }
-
- @Override
public void answer(String callId) {
mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
}
@@ -206,15 +203,12 @@
mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
onAdapterAttached();
break;
- case MSG_CALL:
- call((ConnectionRequest) msg.obj);
+ case MSG_CREATE_CONNECTION:
+ createConnection((ConnectionRequest) msg.obj, msg.arg1 == 1);
break;
case MSG_ABORT:
abort((String) msg.obj);
break;
- case MSG_CREATE_INCOMING_CALL:
- createIncomingCall((ConnectionRequest) msg.obj);
- break;
case MSG_ANSWER:
answer((String) msg.obj);
break;
@@ -394,29 +388,39 @@
return mBinder;
}
- private void call(final ConnectionRequest originalRequest) {
+ /**
+ * This can be used by telecomm to either create a new outgoing call or attach to an existing
+ * incoming call. In either case, telecomm will cycle through a set of services and call
+ * createConnection util a connection service cancels the process or completes it successfully.
+ */
+ private void createConnection(ConnectionRequest originalRequest, boolean isIncoming) {
Log.d(this, "call %s", originalRequest);
- onCreateConnections(
- originalRequest,
- new OutgoingCallResponse<Connection>() {
- @Override
- public void onSuccess(ConnectionRequest request, Connection connection) {
- Log.d(this, "adapter handleSuccessfulOutgoingCall %s", request.getCallId());
- mAdapter.handleSuccessfulOutgoingCall(request);
- addConnection(request.getCallId(), connection);
- }
+ CreateConnectionResponse response = new CreateConnectionResponse<Connection>() {
+ @Override
+ public void onSuccess(ConnectionRequest request, Connection connection) {
+ Log.d(this, "adapter handleCreateConnectionSuccessful %s",
+ request.getCallId());
+ mAdapter.handleCreateConnectionSuccessful(request);
+ addConnection(request.getCallId(), connection);
+ }
- @Override
- public void onFailure(ConnectionRequest request, int code, String msg) {
- mAdapter.handleFailedOutgoingCall(request, code, msg);
- }
+ @Override
+ public void onFailure(ConnectionRequest request, int code, String msg) {
+ // Tell telecomm to try a different service.
+ mAdapter.handleCreateConnectionFailed(request, code, msg);
+ }
- @Override
- public void onCancel(ConnectionRequest request) {
- mAdapter.cancelOutgoingCall(request);
- }
- }
- );
+ @Override
+ public void onCancel(ConnectionRequest request) {
+ // Tell telecomm not to attempt any more services.
+ mAdapter.handleCreateConnectionCancelled(request);
+ }
+ };
+ if (isIncoming) {
+ onCreateIncomingConnection(originalRequest, response);
+ } else {
+ onCreateOutgoingConnection(originalRequest, response);
+ }
}
private void abort(String callId) {
@@ -424,33 +428,6 @@
findConnectionForAction(callId, "abort").onAbort();
}
- private void createIncomingCall(ConnectionRequest originalRequest) {
- Log.d(this, "createIncomingCall %s", originalRequest);
- onCreateIncomingConnection(
- originalRequest,
- new Response<ConnectionRequest, Connection>() {
- @Override
- public void onResult(ConnectionRequest request, Connection... result) {
- if (result != null && result.length != 1) {
- for (Connection c : result) {
- c.onAbort();
- }
- } else {
- addConnection(request.getCallId(), result[0]);
- Log.d(this, "adapter notifyIncomingCall %s", request);
- mAdapter.notifyIncomingCall(request);
- }
- }
-
- @Override
- public void onError(ConnectionRequest request, int code, String msg) {
- Log.d(this, "adapter failed createIncomingCall %s %d %s",
- request, code, msg);
- }
- }
- );
- }
-
private void answer(String callId) {
Log.d(this, "answer %s", callId);
findConnectionForAction(callId, "answer").onAnswer();
@@ -570,7 +547,7 @@
IConnectionService.Stub.asInterface(services.get(i)));
}
mAreAccountsInitialized = true;
- Log.d(this, "remote call services found: " + services);
+ Log.d(this, "remote connection services found: " + services);
maybeRespondToAccountLookup();
}
});
@@ -606,10 +583,16 @@
}
}
+ public final void createRemoteIncomingConnection(
+ ConnectionRequest request,
+ CreateConnectionResponse<RemoteConnection> response) {
+ mRemoteConnectionManager.createRemoteConnection(request, response, true);
+ }
+
public final void createRemoteOutgoingConnection(
ConnectionRequest request,
- OutgoingCallResponse<RemoteConnection> response) {
- mRemoteConnectionManager.createOutgoingConnection(request, response);
+ CreateConnectionResponse<RemoteConnection> response) {
+ mRemoteConnectionManager.createRemoteConnection(request, response, false);
}
/**
@@ -620,14 +603,25 @@
}
/**
- * Create a Connection given a request.
+ * Create a Connection given an incoming request. This is used to attach to existing incoming
+ * calls.
*
- * @param request Data encapsulating details of the desired Connection.
+ * @param request Details about the incoming call.
* @param callback A callback for providing the result.
*/
- protected void onCreateConnections(
+ protected void onCreateIncomingConnection(
ConnectionRequest request,
- OutgoingCallResponse<Connection> callback) {}
+ CreateConnectionResponse<Connection> callback) {}
+
+ /**
+ * Create a Connection given an outgoing request. This is used to initiate new outgoing calls.
+ *
+ * @param request Details about the outgoing call.
+ * @param callback A callback for providing the result.
+ */
+ protected void onCreateOutgoingConnection(
+ ConnectionRequest request,
+ CreateConnectionResponse<Connection> callback) {}
/**
* Returns a new or existing conference connection when the the user elects to convert the
@@ -645,21 +639,6 @@
Response<String, Connection> callback) {}
/**
- * Create a Connection to match an incoming connection notification.
- *
- * IMPORTANT: If the incoming connection has a phone number (or other handle) that the user
- * is not supposed to be able to see (e.g. it is PRESENTATION_RESTRICTED), then a compliant
- * ConnectionService implementation MUST NOT reveal this phone number as part of the Intent
- * it sends to notify Telecomm of an incoming connection.
- *
- * @param request Data encapsulating details of the desired Connection.
- * @param callback A callback for providing the result.
- */
- protected void onCreateIncomingConnection(
- ConnectionRequest request,
- Response<ConnectionRequest, Connection> callback) {}
-
- /**
* Notifies that a connection has been added to this connection service and sent to Telecomm.
*
* @param connection The connection which was added.
diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
index b90dec3..a812fa4 100644
--- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
@@ -73,65 +73,28 @@
}
}
- /**
- * Provides Telecomm with the details of an incoming call. An invocation of this method must
- * follow {@link ConnectionService#setIncomingCallId} and use the call ID specified therein.
- * Upon the invocation of this method, Telecomm will bring up the incoming-call interface where
- * the user can elect to answer or reject a call.
- *
- * @param request The connection request.
- */
- void notifyIncomingCall(ConnectionRequest request) {
+ void handleCreateConnectionSuccessful(ConnectionRequest request) {
for (IConnectionServiceAdapter adapter : mAdapters) {
try {
- adapter.notifyIncomingCall(request);
+ adapter.handleCreateConnectionSuccessful(request);
} catch (RemoteException e) {
}
}
}
- /**
- * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
- *
- * @param request The originating request for a connection.
- */
- void handleSuccessfulOutgoingCall(ConnectionRequest request) {
+ void handleCreateConnectionFailed(ConnectionRequest request, int errorCode, String errorMsg) {
for (IConnectionServiceAdapter adapter : mAdapters) {
try {
- adapter.handleSuccessfulOutgoingCall(request);
+ adapter.handleCreateConnectionFailed(request, errorCode, errorMsg);
} catch (RemoteException e) {
}
}
}
- /**
- * Tells Telecomm that an attempt to place the specified outgoing call failed.
- *
- * @param request The originating request for a connection.
- * @param errorCode The error code associated with the failed call attempt.
- * @param errorMsg The error message associated with the failed call attempt.
- */
- void handleFailedOutgoingCall(
- ConnectionRequest request,
- int errorCode,
- String errorMsg) {
+ void handleCreateConnectionCancelled(ConnectionRequest request) {
for (IConnectionServiceAdapter adapter : mAdapters) {
try {
- adapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
- } catch (RemoteException e) {
- }
- }
- }
-
- /**
- * Tells Telecomm to cancel the call.
- *
- * @param request The originating request for a connection.
- */
- void cancelOutgoingCall(ConnectionRequest request) {
- for (IConnectionServiceAdapter adapter : mAdapters) {
- try {
- adapter.cancelOutgoingCall(request);
+ adapter.handleCreateConnectionCancelled(request);
} catch (RemoteException e) {
}
}
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index d8293a5..66cf1df 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -24,7 +24,7 @@
* Receives commands from {@link InCallService} implementations which should be executed by
* Telecomm. When Telecomm binds to a {@link InCallService}, an instance of this class is given to
* the in-call service through which it can manipulate live (active, dialing, ringing) calls. When
- * the in-call service is notified of new calls ({@link InCallService#addCall}), it can use the
+ * the in-call service is notified of new calls, it can use the
* given call IDs to execute commands such as {@link #answerCall} for incoming calls or
* {@link #disconnectCall} for active calls the user would like to end. Some commands are only
* appropriate for calls in certain states; please consult each method for such limitations.
@@ -167,16 +167,15 @@
* A post-dial DTMF string is a string of digits entered after a phone number, when dialed,
* that are immediately sent as DTMF tones to the recipient as soon as the connection is made.
* While these tones are playing, Telecomm will notify the {@link InCallService} that the call
- * is in the {@link InCallService#setPostDial(String,String)} state.
+ * is in the post dial state.
*
* If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_PAUSE} symbol, Telecomm
* will temporarily pause playing the tones for a pre-defined period of time.
*
* If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_WAIT} symbol, Telecomm
* will pause playing the tones and notify the {@link InCallService} that the call is in the
- * {@link InCallService#setPostDialWait(String,String)} state. When the user decides to continue
- * the postdial sequence, the {@link InCallService} should invoke the
- * {@link #postDialContinue(String,boolean)} method.
+ * post dial wait state. When the user decides to continue the postdial sequence, the
+ * {@link InCallService} should invoke the {@link #postDialContinue(String,boolean)} method.
*
* @param callId The unique ID of the call for which postdial string playing should continue.
* @param proceed Whether or not to continue with the post-dial sequence.
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index 7c35020..db8395c 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -45,12 +45,12 @@
private final int mCallerDisplayNamePresentation;
private final GatewayInfo mGatewayInfo;
private final PhoneAccount mAccount;
- private final CallServiceDescriptor mCurrentCallServiceDescriptor;
private final ICallVideoProvider mCallVideoProvider;
private RemoteCallVideoProvider mRemoteCallVideoProvider;
private final String mParentCallId;
private final List<String> mChildCallIds;
private final StatusHints mStatusHints;
+ private final int mVideoState;
/** @hide */
public InCallCall(
@@ -67,11 +67,11 @@
int callerDisplayNamePresentation,
GatewayInfo gatewayInfo,
PhoneAccount account,
- CallServiceDescriptor descriptor,
ICallVideoProvider callVideoProvider,
String parentCallId,
List<String> childCallIds,
- StatusHints statusHints) {
+ StatusHints statusHints,
+ int videoState) {
mId = id;
mState = state;
mDisconnectCauseCode = disconnectCauseCode;
@@ -85,11 +85,11 @@
mCallerDisplayNamePresentation = callerDisplayNamePresentation;
mGatewayInfo = gatewayInfo;
mAccount = account;
- mCurrentCallServiceDescriptor = descriptor;
mCallVideoProvider = callVideoProvider;
mParentCallId = parentCallId;
mChildCallIds = childCallIds;
mStatusHints = statusHints;
+ mVideoState = videoState;
}
/** The unique ID of the call. */
@@ -165,11 +165,6 @@
return mAccount;
}
- /** The descriptor for the call service currently routing this call. */
- public CallServiceDescriptor getCurrentCallServiceDescriptor() {
- return mCurrentCallServiceDescriptor;
- }
-
/**
* Returns an object for remotely communicating through the call video provider's binder.
* @return The call video provider.
@@ -212,6 +207,14 @@
return mStatusHints;
}
+ /**
+ * The video state.
+ * @return The video state of the call.
+ */
+ public int getVideoState() {
+ return mVideoState;
+ }
+
/** Responsible for creating InCallCall objects for deserialized Parcels. */
public static final Parcelable.Creator<InCallCall> CREATOR =
new Parcelable.Creator<InCallCall> () {
@@ -232,18 +235,18 @@
int callerDisplayNamePresentation = source.readInt();
GatewayInfo gatewayInfo = source.readParcelable(classLoader);
PhoneAccount account = source.readParcelable(classLoader);
- CallServiceDescriptor descriptor = source.readParcelable(classLoader);
ICallVideoProvider callVideoProvider =
ICallVideoProvider.Stub.asInterface(source.readStrongBinder());
String parentCallId = source.readString();
List<String> childCallIds = new ArrayList<>();
source.readList(childCallIds, classLoader);
StatusHints statusHints = source.readParcelable(classLoader);
+ int videoState = source.readInt();
return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg,
cannedSmsResponses, capabilities, connectTimeMillis, handle, handlePresentation,
callerDisplayName, callerDisplayNamePresentation, gatewayInfo,
- account, descriptor, callVideoProvider, parentCallId, childCallIds,
- statusHints);
+ account, callVideoProvider, parentCallId, childCallIds, statusHints,
+ videoState);
}
@Override
@@ -274,12 +277,12 @@
destination.writeInt(mCallerDisplayNamePresentation);
destination.writeParcelable(mGatewayInfo, 0);
destination.writeParcelable(mAccount, 0);
- destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
destination.writeStrongBinder(
mCallVideoProvider != null ? mCallVideoProvider.asBinder() : null);
destination.writeString(mParentCallId);
destination.writeList(mChildCallIds);
destination.writeParcelable(mStatusHints, 0);
+ destination.writeInt(mVideoState);
}
@Override
diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java
index 31291fb..028b6e4 100644
--- a/telecomm/java/android/telecomm/InCallService.java
+++ b/telecomm/java/android/telecomm/InCallService.java
@@ -16,8 +16,6 @@
package android.telecomm;
-import android.app.Service;
-import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -31,11 +29,10 @@
* This service is implemented by any app that wishes to provide the user-interface for managing
* phone calls. Telecomm binds to this service while there exists a live (active or incoming)
* call, and uses it to notify the in-call app of any live and and recently disconnected calls.
- * TODO(santoscordon): Needs more/better description of lifecycle once the interface is better
- * defined.
+ *
* TODO(santoscordon): What happens if two or more apps on a given device implement this interface?
*/
-public abstract class InCallService extends Service {
+public abstract class InCallService {
private static final int MSG_SET_IN_CALL_ADAPTER = 1;
private static final int MSG_ADD_CALL = 2;
private static final int MSG_UPDATE_CALL = 3;
@@ -50,21 +47,21 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_IN_CALL_ADAPTER:
- mAdapter = new InCallAdapter((IInCallAdapter) msg.obj);
- onAdapterAttached(mAdapter);
+ mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj));
+ onPhoneCreated(mPhone);
break;
case MSG_ADD_CALL:
- addCall((InCallCall) msg.obj);
+ mPhone.internalAddCall((InCallCall) msg.obj);
break;
case MSG_UPDATE_CALL:
- updateCall((InCallCall) msg.obj);
+ mPhone.internalUpdateCall((InCallCall) msg.obj);
break;
- case MSG_SET_POST_DIAL: {
+ case MSG_SET_POST_DIAL: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
String remaining = (String) args.arg2;
- setPostDial(callId, remaining);
+ mPhone.internalSetPostDial(callId, remaining);
} finally {
args.recycle();
}
@@ -75,17 +72,17 @@
try {
String callId = (String) args.arg1;
String remaining = (String) args.arg2;
- setPostDialWait(callId, remaining);
+ mPhone.internalSetPostDialWait(callId, remaining);
} finally {
args.recycle();
}
break;
}
case MSG_ON_AUDIO_STATE_CHANGED:
- onAudioStateChanged((CallAudioState) msg.obj);
+ mPhone.internalAudioStateChanged((CallAudioState) msg.obj);
break;
case MSG_BRING_TO_FOREGROUND:
- bringToForeground(msg.arg1 == 1);
+ mPhone.internalBringToForeground(msg.arg1 == 1);
break;
default:
break;
@@ -142,85 +139,41 @@
}
}
- private final InCallServiceBinder mBinder;
+ private Phone mPhone;
- private InCallAdapter mAdapter;
+ protected InCallService() {}
- protected InCallService() {
- mBinder = new InCallServiceBinder();
- }
-
- @Override
- public final IBinder onBind(Intent intent) {
- return mBinder;
+ public final IBinder getBinder() {
+ return new InCallServiceBinder();
}
/**
- * @return The attached {@link InCallAdapter} if attached, or null otherwise.
+ * Obtain the {@code Phone} associated with this {@code InCallService}.
+ *
+ * @return The {@code Phone} object associated with this {@code InCallService}, or {@code null}
+ * if the {@code InCallService} is not in a state where it has an associated {@code Phone}.
*/
- protected final InCallAdapter getAdapter() {
- return mAdapter;
+ public Phone getPhone() {
+ return mPhone;
}
/**
- * Lifecycle callback which is called when this {@link InCallService} has been attached
- * to a {@link InCallAdapter}, indicating {@link #getAdapter()} is now safe to use.
+ * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
+ * to start displaying in-call information to the user. Each instance of {@code InCallService}
+ * will have only one {@code Phone}, and this method will be called exactly once in the
+ * lifetime of the {@code InCallService}.
*
- * @param adapter The adapter now attached to this in-call service.
+ * @param phone The {@code Phone} object associated with this {@code InCallService}.
*/
- protected void onAdapterAttached(InCallAdapter adapter) {
- }
+ public void onPhoneCreated(Phone phone) { }
/**
- * Indicates to the in-call app that a new call has been created and an appropriate
- * user-interface should be built and shown to notify the user.
+ * Invoked when a {@code Phone} has been destroyed. This is a signal to the in-call experience
+ * to stop displaying in-call information to the user. This method will be called exactly once
+ * in the lifetime of the {@code InCallService}, and it will always be called after a previous
+ * call to {@link #onPhoneCreated(Phone)}.
*
- * @param call Information about the new call.
+ * @param phone The {@code Phone} object associated with this {@code InCallService}.
*/
- protected abstract void addCall(InCallCall call);
-
- /**
- * Call when information about a call has changed.
- *
- * @param call Information about the new call.
- */
- protected abstract void updateCall(InCallCall call);
-
- /**
- * Indicates to the in-call app that the specified call is active but in a "post-dial" state
- * where Telecomm is now sending some dual-tone multi-frequency signaling (DTMF) tones appended
- * to the dialed number. Normal transitions are to {@link #setPostDialWait(String,String)} when
- * the post-dial string requires user confirmation to proceed, and {@link CallState#ACTIVE} when
- * the post-dial tones are completed.
- *
- * @param callId The identifier of the call changing state.
- * @param remaining The remaining postdial string to be dialed.
- */
- protected abstract void setPostDial(String callId, String remaining);
-
- /**
- * Indicates to the in-call app that the specified call was in the
- * {@link #setPostDial(String,String)} state but is now waiting for user confirmation before the
- * remaining digits can be sent. Normal transitions are to {@link #setPostDial(String,String)}
- * when the user asks Telecomm to proceed with the post-dial sequence and the in-call app
- * informs Telecomm of this by invoking {@link InCallAdapter#postDialContinue(String,boolean)}.
- *
- * @param callId The identifier of the call changing state.
- * @param remaining The remaining postdial string to be dialed.
- */
- protected abstract void setPostDialWait(String callId, String remaining);
-
- /**
- * Called when the audio state changes.
- *
- * @param audioState The new {@link CallAudioState}.
- */
- protected abstract void onAudioStateChanged(CallAudioState audioState);
-
- /**
- * Brings the in-call screen to the foreground.
- *
- * @param showDialpad If true, put up the dialpad when the screen is shown.
- */
- protected abstract void bringToForeground(boolean showDialpad);
+ public void onPhoneDestroyed(Phone phone) { }
}
diff --git a/telecomm/java/android/telecomm/Phone.java b/telecomm/java/android/telecomm/Phone.java
new file mode 100644
index 0000000..9c97b45
--- /dev/null
+++ b/telecomm/java/android/telecomm/Phone.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecomm;
+
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A unified virtual device providing a means of voice (and other) communication on a device.
+ */
+public final class Phone {
+
+ public abstract static class Listener {
+ /**
+ * Called when the audio state changes.
+ *
+ * @param phone The {@code Phone} calling this method.
+ * @param audioState The new {@link CallAudioState}.
+ */
+ public void onAudioStateChanged(Phone phone, CallAudioState audioState) { }
+
+ /**
+ * Called to bring the in-call screen to the foreground. The in-call experience should
+ * respond immediately by coming to the foreground to inform the user of the state of
+ * ongoing {@code Call}s.
+ *
+ * @param phone The {@code Phone} calling this method.
+ * @param showDialpad If true, put up the dialpad when the screen is shown.
+ */
+ public void onBringToForeground(Phone phone, boolean showDialpad) { }
+
+ /**
+ * Called when a {@code Call} has been added to this in-call session. The in-call user
+ * experience should add necessary state listeners to the specified {@code Call} and
+ * immediately start to show the user information about the existence
+ * and nature of this {@code Call}. Subsequent invocations of {@link #getCalls()} will
+ * include this {@code Call}.
+ *
+ * @param phone The {@code Phone} calling this method.
+ * @param call A newly added {@code Call}.
+ */
+ public void onCallAdded(Phone phone, Call call) { }
+
+ /**
+ * Called when a {@code Call} has been removed from this in-call session. The in-call user
+ * experience should remove any state listeners from the specified {@code Call} and
+ * immediately stop displaying any information about this {@code Call}.
+ * Subsequent invocations of {@link #getCalls()} will no longer include this {@code Call}.
+ *
+ * @param phone The {@code Phone} calling this method.
+ * @param call A newly removed {@code Call}.
+ */
+ public void onCallRemoved(Phone phone, Call call) { }
+ }
+
+ // A Map allows us to track each Call by its Telecomm-specified call ID
+ private final Map<String, Call> mCallByTelecommCallId = new ArrayMap<>();
+
+ // A List allows us to keep the Calls in a stable iteration order so that casually developed
+ // user interface components do not incur any spurious jank
+ private final List<Call> mCalls = new ArrayList<>();
+
+ // An unmodifiable view of the above List can be safely shared with subclass implementations
+ private final List<Call> mUnmodifiableCalls = Collections.unmodifiableList(mCalls);
+
+ private final InCallAdapter mInCallAdapter;
+
+ private CallAudioState mAudioState;
+
+ private final List<Listener> mListeners = new ArrayList<>();
+
+ /** {@hide} */
+ Phone(InCallAdapter adapter) {
+ mInCallAdapter = adapter;
+ }
+
+ /** {@hide} */
+ final void internalAddCall(InCallCall inCallCall) {
+ Call call = new Call(this, inCallCall.getId(), mInCallAdapter);
+ mCallByTelecommCallId.put(inCallCall.getId(), call);
+ mCalls.add(call);
+ checkCallTree(inCallCall);
+ call.internalUpdate(inCallCall);
+ fireCallAdded(call);
+ }
+
+ /** {@hide} */
+ final void internalRemoveCall(Call call) {
+ mCallByTelecommCallId.remove(call.internalGetCallId());
+ mCalls.remove(call);
+ fireCallRemoved(call);
+ }
+
+ /** {@hide} */
+ final void internalUpdateCall(InCallCall inCallCall) {
+ Call call = mCallByTelecommCallId.get(inCallCall.getId());
+ if (call != null) {
+ checkCallTree(inCallCall);
+ call.internalUpdate(inCallCall);
+ }
+ }
+
+ /** {@hide} */
+ final void internalSetPostDial(String callId, String remaining) {
+ Call call = mCallByTelecommCallId.get(callId);
+ if (call != null) {
+ call.internalSetPostDial(remaining);
+ }
+ }
+
+ /** {@hide} */
+ final void internalSetPostDialWait(String callId, String remaining) {
+ Call call = mCallByTelecommCallId.get(callId);
+ if (call != null) {
+ call.internalSetPostDialWait(remaining);
+ }
+ }
+
+ /** {@hide} */
+ final void internalAudioStateChanged(CallAudioState callAudioState) {
+ if (!Objects.equals(mAudioState, callAudioState)) {
+ mAudioState = callAudioState;
+ fireAudioStateChanged(callAudioState);
+ }
+ }
+
+ /** {@hide} */
+ final Call internalGetCallByTelecommId(String telecommId) {
+ return mCallByTelecommCallId.get(telecommId);
+ }
+
+ /** {@hide} */
+ final void internalBringToForeground(boolean showDialpad) {
+ fireBringToForeground(showDialpad);
+ }
+
+ /**
+ * Adds a listener to this {@code Phone}.
+ *
+ * @param listener A {@code Listener} object.
+ */
+ public final void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener from this {@code Phone}.
+ *
+ * @param listener A {@code Listener} object.
+ */
+ public final void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Obtains the current list of {@code Call}s to be displayed by this in-call experience.
+ *
+ * @return A list of the relevant {@code Call}s.
+ */
+ public final List<Call> getCalls() {
+ return mUnmodifiableCalls;
+ }
+
+ /**
+ * Sets the microphone mute state. When this request is honored, there will be change to
+ * the {@link #getAudioState()}.
+ *
+ * @param state {@code true} if the microphone should be muted; {@code false} otherwise.
+ */
+ public final void setMuted(boolean state) {
+ mInCallAdapter.mute(state);
+ }
+
+ /**
+ * Sets the audio route (speaker, bluetooth, etc...). When this request is honored, there will
+ * be change to the {@link #getAudioState()}.
+ *
+ * @param route The audio route to use.
+ */
+ public final void setAudioRoute(int route) {
+ mInCallAdapter.setAudioRoute(route);
+ }
+
+ /**
+ * Obtains the current phone call audio state of the {@code Phone}.
+ *
+ * @return An object encapsulating the audio state.
+ */
+ public final CallAudioState getAudioState() {
+ return mAudioState;
+ }
+
+ private void fireCallAdded(Call call) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onCallAdded(this, call);
+ }
+ }
+
+ private void fireCallRemoved(Call call) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onCallRemoved(this, call);
+ }
+ }
+
+ private void fireAudioStateChanged(CallAudioState audioState) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onAudioStateChanged(this, audioState);
+ }
+ }
+
+ private void fireBringToForeground(boolean showDialpad) {
+ Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
+ for (int i = 0; i < listeners.length; i++) {
+ listeners[i].onBringToForeground(this, showDialpad);
+ }
+ }
+
+ private void checkCallTree(InCallCall inCallCall) {
+ if (inCallCall.getParentCallId() != null &&
+ !mCallByTelecommCallId.containsKey(inCallCall.getParentCallId())) {
+ Log.wtf(this, "InCallCall %s has nonexistent parent %s",
+ inCallCall.getId(), inCallCall.getParentCallId());
+ }
+ if (inCallCall.getChildCallIds() != null) {
+ for (int i = 0; i < inCallCall.getChildCallIds().size(); i++) {
+ if (!mCallByTelecommCallId.containsKey(inCallCall.getChildCallIds().get(i))) {
+ Log.wtf(this, "InCallCall %s has nonexistent child %s",
+ inCallCall.getId(), inCallCall.getChildCallIds().get(i));
+ }
+ }
+ }
+ }
+}
diff --git a/telecomm/java/android/telecomm/PhoneAccount.java b/telecomm/java/android/telecomm/PhoneAccount.java
index 4e440d8..c1eec83 100644
--- a/telecomm/java/android/telecomm/PhoneAccount.java
+++ b/telecomm/java/android/telecomm/PhoneAccount.java
@@ -17,52 +17,64 @@
package android.telecomm;
import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import java.util.MissingResourceException;
import java.util.Objects;
/**
* Represents a distinct account, line of service or call placement method that
* the system can use to place phone calls.
*/
-public final class PhoneAccount implements Parcelable {
+public class PhoneAccount implements Parcelable {
- private static final int NO_DENSITY = -1;
- private static final String LOG_TAG = "Account";
+ /**
+ * Flag indicating that this {@code PhoneAccount} can act as a call manager for traditional
+ * SIM-based telephony calls. The {@link ConnectionService} associated with this phone-account
+ * will be allowed to manage SIM-based phone calls including using its own proprietary
+ * phone-call implementation (like VoIP calling) to make calls instead of the telephony stack.
+ * When a user opts to place a call using the SIM-based telephony stack, the connection-service
+ * associated with this phone-account will be attempted first if the user has explicitly
+ * selected it to be used as the default call-manager.
+ * <p>
+ * See {@link #getCapabilities}
+ */
+ public static final int CAPABILITY_SIM_CALL_MANAGER = 0x1;
- private final ComponentName mComponentName;
- private final String mId;
- private final Uri mHandle;
- private final String mLabel;
- private final String mShortDescription;
- private final boolean mIsEnabled;
- private final boolean mIsSystemDefault;
+ /**
+ * Flag indicating that this {@code PhoneAccount} can make phone calls in place of traditional
+ * SIM-based telephony calls. This account will be treated as a distinct method for placing
+ * calls alongside the traditional SIM-based telephony stack. This flag is distinct from
+ * {@link #CAPABILITY_SIM_CALL_MANAGER} in that it is not allowed to manage calls from or use
+ * the built-in telephony stack to place its calls.
+ * <p>
+ * See {@link #getCapabilities}
+ */
+ public static final int CAPABILITY_CALL_PROVIDER = 0x2;
+
+ /**
+ * Flag indicating that this {@code PhoneAccount} represents a built-in PSTN SIM subscription.
+ * <p>
+ * Only the android framework can set this capability on a phone-account.
+ */
+ public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4;
+
+ private ComponentName mComponentName;
+ private String mId;
+ private Uri mHandle;
+ private int mCapabilities;
public PhoneAccount(
ComponentName componentName,
String id,
Uri handle,
- String label,
- String shortDescription,
- boolean isEnabled,
- boolean isSystemDefault) {
+ int capabilities) {
mComponentName = componentName;
mId = id;
mHandle = handle;
- mLabel = label;
- mShortDescription = shortDescription;
- mIsSystemDefault = isSystemDefault;
- mIsEnabled = isEnabled;
+ mCapabilities = capabilities;
}
/**
@@ -87,8 +99,8 @@
/**
* The handle (e.g., a phone number) associated with this {@code PhoneAccount}. This represents
- * the destination from which outgoing calls using this {@code PhoneAccount} will appear to come
- * from, if applicable, and the destination to which incoming calls using this
+ * the destination from which outgoing calls using this {@code PhoneAccount} will appear to
+ * come, if applicable, and the destination to which incoming calls using this
* {@code PhoneAccount} may be addressed.
*
* @return A handle expressed as a {@code Uri}, for example, a phone number.
@@ -98,76 +110,23 @@
}
/**
- * A short string label describing this {@code PhoneAccount}.
+ * The capabilities of this {@code PhoneAccount}.
*
- * @param context The invoking {@code Context}, used for retrieving resources.
- *
- * TODO(ihab): If don't need context, remove param
- *
- * @return A label for this {@code PhoneAccount}.
+ * @return A bit field of flags describing this {@code PhoneAccount}'s capabilities.
*/
- public String getLabel(Context context) {
- return mLabel;
+ public int getCapabilities() {
+ return mCapabilities;
}
- /**
- * A short paragraph describing this {@code PhoneAccount}.
- *
- * @param context The invoking {@code Context}, used for retrieving resources.
- *
- * TODO(ihab): If don't need context, remove param
- *
- * @return A description for this {@code PhoneAccount}.
- */
- public String getShortDescription(Context context) {
- return mShortDescription;
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mComponentName) + Objects.hashCode(mId) +
+ Objects.hashCode(mHandle) + mCapabilities;
}
- // TODO(ihab): Representation of the icons
//
- // Refactor to pass a Bitmap (scale it at runtime), but if they don't pass one, fall
- // back to the android:icon attr in the manifest (<service /> first, <application /> second)
-
- /**
- * An icon to represent this {@code PhoneAccount} in a user interface.
- *
- * @param context The invoking {@code Context}, used for retrieving resources.
- *
- * @return An icon for this {@code PhoneAccount}.
- */
- public Drawable getIcon(Context context) {
- return null; // TODO(ihab): See above
- }
-
- /**
- * An icon to represent this {@code PhoneAccount} in a user interface.
- *
- * @param context The invoking {@code Context}, used for retrieving resources.
- * @param density A display density from {@link DisplayMetrics}.
- *
- * @return An icon for this {@code PhoneAccount}.
- */
- public Drawable getIcon(Context context, int density) {
- return null; // TODO(ihab): See above
- }
-
- /**
- * Whether this {@code PhoneAccount} is enabled for use.
- *
- * @return {@code true} if this {@code PhoneAccount} is enabled.
- */
- public boolean isEnabled() {
- return mIsEnabled;
- }
-
- /**
- * Whether this {@code PhoneAccount} is the system default.
- *
- * @return {@code true} if this {@code PhoneAccount} is the system default.
- */
- public boolean isSystemDefault() {
- return mIsSystemDefault;
- }
+ // Parcelable implementation.
+ //
@Override
public int describeContents() {
@@ -179,18 +138,16 @@
out.writeParcelable(mComponentName, flags);
out.writeString(mId);
out.writeString(mHandle != null ? mHandle.toString() : "");
- out.writeString(mLabel);
- out.writeString(mShortDescription);
- out.writeInt(mIsEnabled ? 1 : 0);
- out.writeInt(mIsSystemDefault ? 1 : 0);
+ out.writeInt(mCapabilities);
}
- public static final Creator<PhoneAccount> CREATOR
- = new Creator<PhoneAccount>() {
+ public static final Creator<PhoneAccount> CREATOR = new Creator<PhoneAccount>() {
+ @Override
public PhoneAccount createFromParcel(Parcel in) {
return new PhoneAccount(in);
}
+ @Override
public PhoneAccount[] newArray(int size) {
return new PhoneAccount[size];
}
@@ -201,22 +158,6 @@
mId = in.readString();
String uriString = in.readString();
mHandle = uriString.length() > 0 ? Uri.parse(uriString) : null;
- mLabel = in.readString();
- mShortDescription = in.readString();
- mIsEnabled = in.readInt() == 1;
- mIsSystemDefault = in.readInt() == 1;
- }
-
- @Override
- public boolean equals(Object other) {
- return
- other instanceof PhoneAccount &&
- Objects.equals(mComponentName, ((PhoneAccount) other).mComponentName) &&
- Objects.equals(mId, ((PhoneAccount) other).mId);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(mComponentName) + Objects.hashCode(mId);
+ mCapabilities = in.readInt();
}
}
diff --git a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl b/telecomm/java/android/telecomm/PhoneAccountMetadata.aidl
similarity index 77%
rename from telecomm/java/android/telecomm/CallServiceDescriptor.aidl
rename to telecomm/java/android/telecomm/PhoneAccountMetadata.aidl
index f517c73..55b8900 100644
--- a/telecomm/java/android/telecomm/CallServiceDescriptor.aidl
+++ b/telecomm/java/android/telecomm/PhoneAccountMetadata.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright 2014, The Android Open Source Project
+ * 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
+ * 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,
@@ -16,4 +16,7 @@
package android.telecomm;
-parcelable CallServiceDescriptor;
+/**
+ * {@hide}
+ */
+parcelable PhoneAccountMetadata;
diff --git a/telecomm/java/android/telecomm/PhoneAccountMetadata.java b/telecomm/java/android/telecomm/PhoneAccountMetadata.java
new file mode 100644
index 0000000..20a4d47
--- /dev/null
+++ b/telecomm/java/android/telecomm/PhoneAccountMetadata.java
@@ -0,0 +1,140 @@
+/*
+ * 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 android.telecomm;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.MissingResourceException;
+
+/**
+ * Provides user interface description information for a {@code PhoneAccount}.
+ */
+public class PhoneAccountMetadata implements Parcelable {
+ private PhoneAccount mAccount;
+ private int mIconResId;
+ private String mLabel;
+ private String mShortDescription;
+
+ public PhoneAccountMetadata(
+ PhoneAccount account,
+ int iconResId,
+ String label,
+ String shortDescription) {
+ mAccount = account;
+ mIconResId = iconResId;
+ mLabel = label;
+ mShortDescription = shortDescription;
+ }
+
+ /**
+ * The {@code PhoneAccount} to which this metadata pertains.
+ *
+ * @return A {@code PhoneAccount}.
+ */
+ public PhoneAccount getAccount() {
+ return mAccount;
+ }
+
+ /**
+ * A short string label describing a {@code PhoneAccount}.
+ *
+ * @return A label for this {@code PhoneAccount}.
+ */
+ public String getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * A short paragraph describing a {@code PhoneAccount}.
+ *
+ * @return A description for this {@code PhoneAccount}.
+ */
+ public String getShortDescription() {
+ return mShortDescription;
+ }
+
+ /**
+ * An icon to represent this {@code PhoneAccount} in a user interface.
+ *
+ * @return An icon for this {@code PhoneAccount}.
+ */
+ public Drawable getIcon(Context context) {
+ return getIcon(context, mIconResId);
+ }
+
+ private Drawable getIcon(Context context, int resId) {
+ Context packageContext;
+ try {
+ packageContext = context.createPackageContext(
+ mAccount.getComponentName().getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(this, "Cannot find package %s", mAccount.getComponentName().getPackageName());
+ return null;
+ }
+ try {
+ return packageContext.getResources().getDrawable(resId);
+ } catch (MissingResourceException e) {
+ Log.e(this, e, "Cannot find icon %d in package %s",
+ resId, mAccount.getComponentName().getPackageName());
+ return null;
+ }
+ }
+
+ //
+ // Parcelable implementation
+ //
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mAccount, 0);
+ out.writeInt(mIconResId);
+ out.writeString(mLabel);
+ out.writeString(mShortDescription);
+ }
+
+ public static final Creator<PhoneAccountMetadata> CREATOR
+ = new Creator<PhoneAccountMetadata>() {
+ @Override
+ public PhoneAccountMetadata createFromParcel(Parcel in) {
+ return new PhoneAccountMetadata(in);
+ }
+
+ @Override
+ public PhoneAccountMetadata[] newArray(int size) {
+ return new PhoneAccountMetadata[size];
+ }
+ };
+
+ private PhoneAccountMetadata(Parcel in) {
+ mAccount = in.readParcelable(getClass().getClassLoader());
+ mIconResId = in.readInt();
+ mLabel = in.readString();
+ mShortDescription = in.readString();
+ }
+}
diff --git a/telecomm/java/android/telecomm/RemoteCallVideoProvider.java b/telecomm/java/android/telecomm/RemoteCallVideoProvider.java
index 856d321..a49076a 100644
--- a/telecomm/java/android/telecomm/RemoteCallVideoProvider.java
+++ b/telecomm/java/android/telecomm/RemoteCallVideoProvider.java
@@ -20,63 +20,92 @@
import android.os.RemoteException;
import android.view.Surface;
-import com.android.internal.telecomm.ICallVideoClient;
import com.android.internal.telecomm.ICallVideoProvider;
-public class RemoteCallVideoProvider implements IBinder.DeathRecipient {
+public class RemoteCallVideoProvider {
private final ICallVideoProvider mCallVideoProvider;
+ private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ mCallVideoProvider.asBinder().unlinkToDeath(this, 0);
+ }
+ };
+
+ /** {@hide} */
RemoteCallVideoProvider(ICallVideoProvider callVideoProvider) throws RemoteException {
mCallVideoProvider = callVideoProvider;
- mCallVideoProvider.asBinder().linkToDeath(this, 0);
+ mCallVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
}
- @Override
- public void binderDied() {
- mCallVideoProvider.asBinder().unlinkToDeath(this, 0);
- }
-
- public void setCallVideoClient(CallVideoClient callVideoClient) throws RemoteException {
- mCallVideoProvider.setCallVideoClient(callVideoClient.getBinder());
+ public void setCallVideoClient(CallVideoClient callVideoClient) {
+ try {
+ mCallVideoProvider.setCallVideoClient(callVideoClient.getBinder());
+ } catch (RemoteException e) {
+ }
}
public void setCamera(String cameraId) throws RemoteException {
mCallVideoProvider.setCamera(cameraId);
}
- public void setPreviewSurface(Surface surface) throws RemoteException {
- mCallVideoProvider.setPreviewSurface(surface);
+ public void setPreviewSurface(Surface surface) {
+ try {
+ mCallVideoProvider.setPreviewSurface(surface);
+ } catch (RemoteException e) {
+ }
}
- public void setDisplaySurface(Surface surface) throws RemoteException {
- mCallVideoProvider.setDisplaySurface(surface);
+ public void setDisplaySurface(Surface surface) {
+ try {
+ mCallVideoProvider.setDisplaySurface(surface);
+ } catch (RemoteException e) {
+ }
}
- public void setDeviceOrientation(int rotation) throws RemoteException {
- mCallVideoProvider.setDeviceOrientation(rotation);
+ public void setDeviceOrientation(int rotation) {
+ try {
+ mCallVideoProvider.setDeviceOrientation(rotation);
+ } catch (RemoteException e) {
+ }
}
public void setZoom(float value) throws RemoteException {
mCallVideoProvider.setZoom(value);
}
- public void sendSessionModifyRequest(VideoCallProfile requestProfile) throws RemoteException {
- mCallVideoProvider.sendSessionModifyRequest(requestProfile);
+ public void sendSessionModifyRequest(VideoCallProfile requestProfile) {
+ try {
+ mCallVideoProvider.sendSessionModifyRequest(requestProfile);
+ } catch (RemoteException e) {
+ }
}
- public void sendSessionModifyResponse(VideoCallProfile responseProfile) throws RemoteException {
- mCallVideoProvider.sendSessionModifyResponse(responseProfile);
+ public void sendSessionModifyResponse(VideoCallProfile responseProfile) {
+ try {
+ mCallVideoProvider.sendSessionModifyResponse(responseProfile);
+ } catch (RemoteException e) {
+ }
}
- public void requestCameraCapabilities() throws RemoteException {
- mCallVideoProvider.requestCameraCapabilities();
+ public void requestCameraCapabilities() {
+ try {
+ mCallVideoProvider.requestCameraCapabilities();
+ } catch (RemoteException e) {
+ }
}
- public void requestCallDataUsage() throws RemoteException {
- mCallVideoProvider.requestCallDataUsage();
+ public void requestCallDataUsage() {
+ try {
+ mCallVideoProvider.requestCallDataUsage();
+ } catch (RemoteException e) {
+ }
}
- public void setPauseImage(String uri) throws RemoteException {
- mCallVideoProvider.setPauseImage(uri);
+ public void setPauseImage(String uri) {
+ try {
+ mCallVideoProvider.setPauseImage(uri);
+ } catch (RemoteException e) {
+ }
}
}
\ No newline at end of file
diff --git a/telecomm/java/android/telecomm/RemoteConnectionManager.java b/telecomm/java/android/telecomm/RemoteConnectionManager.java
index 9cffdcc..0a0b245 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionManager.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionManager.java
@@ -54,9 +54,10 @@
return accounts;
}
- public void createOutgoingConnection(
+ public void createRemoteConnection(
ConnectionRequest request,
- final ConnectionService.OutgoingCallResponse response) {
+ ConnectionService.CreateConnectionResponse response,
+ boolean isIncoming) {
PhoneAccount account = request.getAccount();
if (account == null) {
throw new IllegalArgumentException("account must be specified.");
@@ -67,7 +68,7 @@
throw new UnsupportedOperationException("account not supported: " + componentName);
} else {
RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
- remoteService.createOutgoingConnection(request, response);
+ remoteService.createRemoteConnection(request, response, isIncoming);
}
}
}
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
index a436af2..7fd8f93 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionService.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -44,40 +44,34 @@
private String mConnectionId;
private ConnectionRequest mPendingRequest;
- private ConnectionService.OutgoingCallResponse<RemoteConnection> mPendingOutgoingCallResponse;
+ private ConnectionService.CreateConnectionResponse<RemoteConnection> mPendingResponse;
// Remote connection services only support a single connection.
private RemoteConnection mConnection;
private final IConnectionServiceAdapter mAdapter = new IConnectionServiceAdapter.Stub() {
-
@Override
- public void notifyIncomingCall(ConnectionRequest request) {
- Log.w(this, "notifyIncomingCall not implemented in Remote connection");
- }
-
- @Override
- public void handleSuccessfulOutgoingCall(ConnectionRequest request) {
+ public void handleCreateConnectionSuccessful(ConnectionRequest request) {
if (isPendingConnection(request.getCallId())) {
mConnection = new RemoteConnection(mConnectionService, request.getCallId());
- mPendingOutgoingCallResponse.onSuccess(request, mConnection);
+ mPendingResponse.onSuccess(request, mConnection);
clearPendingInformation();
}
}
@Override
- public void handleFailedOutgoingCall(
+ public void handleCreateConnectionFailed(
ConnectionRequest request, int errorCode, String errorMessage) {
if (isPendingConnection(request.getCallId())) {
- mPendingOutgoingCallResponse.onFailure(request, errorCode, errorMessage);
+ mPendingResponse.onFailure(request, errorCode, errorMessage);
mConnectionId = null;
clearPendingInformation();
}
}
@Override
- public void cancelOutgoingCall(ConnectionRequest request) {
+ public void handleCreateConnectionCancelled(ConnectionRequest request) {
if (isPendingConnection(request.getCallId())) {
- mPendingOutgoingCallResponse.onCancel(request);
+ mPendingResponse.onCancel(request);
mConnectionId = null;
clearPendingInformation();
}
@@ -226,12 +220,10 @@
release();
}
- /**
- * Places an outgoing call.
- */
- final void createOutgoingConnection(
+ final void createRemoteConnection(
ConnectionRequest request,
- ConnectionService.OutgoingCallResponse<RemoteConnection> response) {
+ ConnectionService.CreateConnectionResponse<RemoteConnection> response,
+ boolean isIncoming) {
if (mConnectionId == null) {
String id = UUID.randomUUID().toString();
@@ -243,9 +235,9 @@
request.getExtras(),
request.getVideoState());
try {
- mConnectionService.call(newRequest);
+ mConnectionService.createConnection(newRequest, isIncoming);
mConnectionId = id;
- mPendingOutgoingCallResponse = response;
+ mPendingResponse = response;
mPendingRequest = request;
} catch (RemoteException e) {
response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, e.toString());
@@ -255,9 +247,6 @@
}
}
- // TODO(santoscordon): Handle incoming connections
- // public final void handleIncomingConnection() {}
-
final List<PhoneAccount> lookupAccounts(Uri handle) {
// TODO(santoscordon): Update this so that is actually calls into the RemoteConnection
// each time.
@@ -266,10 +255,7 @@
mComponentName,
null /* id */,
null /* handle */,
- "" /* label */,
- "" /* shortDescription */,
- true /* isEnabled */,
- false /* isSystemDefault */));
+ 0 /* capabilities */));
return accounts;
}
@@ -282,7 +268,7 @@
}
private boolean isPendingConnection(String id) {
- return TextUtils.equals(mConnectionId, id) && mPendingOutgoingCallResponse != null;
+ return TextUtils.equals(mConnectionId, id) && mPendingResponse != null;
}
private boolean isCurrentConnection(String id) {
@@ -291,7 +277,7 @@
private void clearPendingInformation() {
mPendingRequest = null;
- mPendingOutgoingCallResponse = null;
+ mPendingResponse = null;
}
private void destroyConnection() {
diff --git a/telecomm/java/android/telecomm/TelecommConstants.java b/telecomm/java/android/telecomm/TelecommConstants.java
index b9fb40c..a94841f 100644
--- a/telecomm/java/android/telecomm/TelecommConstants.java
+++ b/telecomm/java/android/telecomm/TelecommConstants.java
@@ -16,6 +16,7 @@
package android.telecomm;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.TelephonyManager;
@@ -31,9 +32,9 @@
* to find and bind to the appropriate {@link android.telecomm.ConnectionService} which
* Telecomm will ultimately use to control and get information about the call.</p>
*
- * <p>Input: get*Extra field {@link #EXTRA_CALL_SERVICE_DESCRIPTOR} contains the component name
- * of the {@link android.telecomm.ConnectionService} that Telecomm should bind to. Telecomm
- * will then ask the call service for more information about the call prior to showing any UI.
+ * <p>Input: get*Extra field {@link #EXTRA_PHONE_ACCOUNT} contains the component name of the
+ * {@link android.telecomm.ConnectionService} that Telecomm should bind to. Telecomm will then
+ * ask the connection service for more information about the call prior to showing any UI.
*
* TODO(santoscordon): Needs permissions.
* TODO(santoscordon): Consider moving this into a simple method call on a system service.
@@ -41,16 +42,17 @@
public static final String ACTION_INCOMING_CALL = "android.intent.action.INCOMING_CALL";
/**
- * The service action used to bind to {@link CallServiceProvider} implementations.
- */
- public static final String ACTION_CALL_SERVICE_PROVIDER = CallServiceProvider.class.getName();
-
- /**
* The service action used to bind to {@link ConnectionService} implementations.
*/
public static final String ACTION_CONNECTION_SERVICE = ConnectionService.class.getName();
/**
+ * The {@link Intent} action used to configure a {@link ConnectionService}.
+ */
+ public static final String ACTION_CONNECTION_SERVICE_CONFIGURE =
+ "android.intent.action.CONNECTION_SERVICE_CONFIGURE";
+
+ /**
* Optional extra for {@link Intent#ACTION_CALL} containing a boolean that determines whether
* the speakerphone should be automatically turned on for an outgoing call.
*/
@@ -69,11 +71,15 @@
"android.intent.extra.START_CALL_WITH_VIDEO_STATE";
/**
- * Extra for {@link #ACTION_INCOMING_CALL} containing the {@link CallServiceDescriptor} that
- * describes the call service to use for the incoming call.
+ * The extra used with an {@link android.content.Intent#ACTION_CALL},
+ * {@link #ACTION_INCOMING_CALL}, {@link android.content.Intent#ACTION_DIAL} {@code Intent} to
+ * specify a {@link PhoneAccount} to use when making the call.
+ *
+ * <p class="note">
+ * Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
*/
- public static final String EXTRA_CALL_SERVICE_DESCRIPTOR =
- "android.intent.extra.CALL_SERVICE_DESCRIPTOR";
+ public static final String EXTRA_PHONE_ACCOUNT = "android.intent.extra.PHONE_ACCOUNT";
/**
* Optional extra for {@link #ACTION_INCOMING_CALL} containing a {@link Bundle} which contains
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index 1bb18f2..fcd2eba 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -23,31 +23,30 @@
import com.android.internal.telecomm.ITelecommService;
+import java.util.List;
+
/**
* Provides access to Telecomm-related functionality.
* TODO(santoscordon): Move this all into PhoneManager.
* @hide
*/
public class TelecommManager {
+
+ /**
+ * The extra used with an {@link android.content.Intent#ACTION_CALL} or
+ * {@link android.content.Intent#ACTION_DIAL} {@code Intent} to specify a {@link PhoneAccount}
+ * to use when making the call.
+ *
+ * <p class="note">
+ * Retrieve with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_PHONE_ACCOUNT = "account";
+
private static final String TAG = "TelecommManager";
private static final String TELECOMM_SERVICE_NAME = "telecomm";
private final Context mContext;
- private final ITelecommService mService;
-
- /**
- * @hide
- */
- public TelecommManager(Context context, ITelecommService service) {
- Context appContext = context.getApplicationContext();
- if (appContext != null) {
- mContext = appContext;
- } else {
- mContext = context;
- }
-
- mService = service;
- }
/**
* @hide
@@ -59,6 +58,103 @@
/**
* @hide
*/
+ public TelecommManager(Context context) {
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ }
+
+ /**
+ * Return a list of {@link PhoneAccount}s which can be used to make and receive phone calls.
+ *
+ * @see #EXTRA_PHONE_ACCOUNT
+ * @return A list of {@code PhoneAccount} objects.
+ */
+ public List<PhoneAccount> getEnabledPhoneAccounts() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecommService().getEnabledPhoneAccounts();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#getEnabledPhoneAccounts", e);
+ }
+ return null;
+ }
+
+ /**
+ * Return the metadata for a specified {@link PhoneAccount}. Metadata includes resources which
+ * can be used in a user interface.
+ *
+ * @param account The {@link PhoneAccount}.
+ *
+ * @return The metadata for the account.
+ */
+ public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccount account) {
+ try {
+ if (isServiceConnected()) {
+ return getTelecommService().getPhoneAccountMetadata(account);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#getPhoneAccountMetadata", e);
+ }
+ return null;
+ }
+
+ /**
+ * Register a {@link PhoneAccount} for use by the system.
+ *
+ * @param account The {@link PhoneAccount}.
+ * @param metadata The metadata for the account.
+ */
+ public void registerPhoneAccount(PhoneAccount account, PhoneAccountMetadata metadata) {
+ try {
+ if (isServiceConnected()) {
+ getTelecommService().registerPhoneAccount(account, metadata);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#registerPhoneAccount", e);
+ }
+ }
+
+ /**
+ * Remove a {@link PhoneAccount} registration from the system.
+ *
+ * @param account An Account.
+ */
+ public void unregisterPhoneAccount(PhoneAccount account) {
+ try {
+ if (isServiceConnected()) {
+ getTelecommService().unregisterPhoneAccount(account);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#unregisterPhoneAccount", e);
+ }
+ }
+
+ /**
+ * Remove all Accounts for a given package from the system.
+ *
+ * @param packageName A package name that may have registered Accounts.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void clearAccounts(String packageName) {
+ try {
+ if (isServiceConnected()) {
+ getTelecommService().clearAccounts(packageName);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelecommService#clearAccounts", e);
+ }
+ }
+
+ /**
+ * @hide
+ */
@SystemApi
public ComponentName getDefaultPhoneApp() {
try {
@@ -108,7 +204,7 @@
/**
* Ends an ongoing call. TODO(santoscordon): L-release - need to convert all invocations of
- * ITelephony#endCall to use this method (clockwork & gearhead).
+ * ITelecommService#endCall to use this method (clockwork & gearhead).
*
* @hide
*/
@@ -127,7 +223,7 @@
/**
* If there is a ringing incoming call, this method accepts the call on behalf of the user.
* TODO(santoscordon): L-release - need to convert all invocation of
- * ITelephony#answerRingingCall to use this method (clockwork & gearhead).
+ * ITelecommService#answerRingingCall to use this method (clockwork & gearhead).
*
* @hide
*/
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl
deleted file mode 100644
index 10d73be..0000000
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telecomm;
-
-import android.os.IBinder;
-import android.telecomm.CallServiceDescriptor;
-import java.util.List;
-
-/**
- * Internal remote interface for call service lookup response.
- *
- * @see android.telecomm.CallServiceLookupResponse
- *
- * @hide
- */
-oneway interface ICallServiceLookupResponse {
- void setCallServiceDescriptors(in List<CallServiceDescriptor> callServiceDescriptors);
-}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl
deleted file mode 100644
index 96daeed..0000000
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.telecomm;
-
-import android.telecomm.CallServiceDescriptor;
-
-import com.android.internal.telecomm.ICallServiceLookupResponse;
-
-/**
- * Internal remote interface for call service providers.
- *
- * @see android.telecomm.CallServiceProvider
- *
- * @hide
- */
-oneway interface ICallServiceProvider {
- void lookupCallServices(in ICallServiceLookupResponse response);
-}
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
index 16d2edf..9360219 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
@@ -32,12 +32,10 @@
oneway interface IConnectionService {
void addConnectionServiceAdapter(in IConnectionServiceAdapter adapter);
- void call(in ConnectionRequest request);
+ void createConnection(in ConnectionRequest request, boolean isIncoming);
void abort(String callId);
- void createIncomingCall(in ConnectionRequest request);
-
void answer(String callId);
void reject(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
index bc67eab..b36f72c 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
@@ -31,13 +31,12 @@
* {@hide}
*/
oneway interface IConnectionServiceAdapter {
- void notifyIncomingCall(in ConnectionRequest request);
+ void handleCreateConnectionSuccessful(in ConnectionRequest request);
- void handleSuccessfulOutgoingCall(in ConnectionRequest request);
+ void handleCreateConnectionFailed(
+ in ConnectionRequest request, int errorCode, String errorMessage);
- void handleFailedOutgoingCall(in ConnectionRequest request, int errorCode, String errorMessage);
-
- void cancelOutgoingCall(in ConnectionRequest request);
+ void handleCreateConnectionCancelled(in ConnectionRequest request);
void setActive(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index 30e4bdc..3334385 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.telecomm.PhoneAccount;
+import android.telecomm.PhoneAccountMetadata;
/**
* Interface used to interact with Telecomm. Mostly this is used by TelephonyManager for passing
@@ -33,22 +34,32 @@
void showCallScreen(boolean showDialpad);
/**
- * Gets a list of accounts.
+ * @see TelecommManager#getEnabledPhoneAccounts
*/
- List<PhoneAccount> getAccounts();
+ List<PhoneAccount> getEnabledPhoneAccounts();
/**
- * Sets the enabled state of a given account.
+ * @see TelecommManager#getPhoneAccountMetadata
*/
- void setEnabled(in PhoneAccount account, boolean enabled);
+ PhoneAccountMetadata getPhoneAccountMetadata(in PhoneAccount account);
/**
- * Sets a given account as the system default.
+ * @see TelecommManager#registerPhoneAccount
*/
- void setSystemDefault(in PhoneAccount account);
+ void registerPhoneAccount(in PhoneAccount account, in PhoneAccountMetadata metadata);
/**
- * Returns the component name of the default phone application.
+ * @see TelecommManager#unregisterPhoneAccount
+ */
+ void unregisterPhoneAccount(in PhoneAccount account);
+
+ /**
+ * @see TelecommManager#clearAccounts
+ */
+ void clearAccounts(String packageName);
+
+ /**
+ * @see TelecommManager#getDefaultPhoneApp
*/
ComponentName getDefaultPhoneApp();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 91ce73a..c1eb843 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -298,17 +298,6 @@
public static final String EXTRA_INCOMING_NUMBER = "incoming_number";
/**
- * The lookup key used with an {@link android.content.Intent#ACTION_CALL} or
- * {@link android.content.Intent#ACTION_DIAL} {@code Intent} for a {@link PhoneAccount}
- * object indicating a preference when making a phone connection.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getParcelableExtra(String)}.
- */
- public static final String EXTRA_ACCOUNT = "account";
-
- /**
* Broadcast intent action indicating that a precise call state
* (cellular) on the device has changed.
*
@@ -3207,42 +3196,6 @@
}
/**
- * Return a list of Accounts that can be used to indicate a preference when making
- * a phone call.
- *
- * @see #EXTRA_ACCOUNT
- * @return A list of {@code Accouint} objects.
- */
- public List<PhoneAccount> getAccounts() {
- try {
- return getTelecommService().getAccounts();
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#getAccounts", e);
- }
- return null;
- }
-
- /** @hide */
- @SystemApi
- public void setEnabled(PhoneAccount account, boolean enabled) {
- try {
- getTelecommService().setEnabled(account, enabled);
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#setEnabled", e);
- }
- }
-
- /** @hide */
- @SystemApi
- public void setSystemDefault(PhoneAccount account) {
- try {
- getTelecommService().setSystemDefault(account);
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#setSystemDefault", e);
- }
- }
-
- /**
* Set whether Android should display a simplified Mobile Network Settings UI.
* The setting won't be persisted during power cycle.
* <p>
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 4592717..d41ceda 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -650,7 +650,7 @@
*
* TODO: Add a link to documentation.
*
- * @return carrier privelege status defined in TelephonyManager.
+ * @return carrier privilege status defined in TelephonyManager.
*/
int hasCarrierPrivileges();
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallListener.aidl b/telephony/java/com/android/internal/telephony/IThirdPartyCallListener.aidl
deleted file mode 100644
index bcf2d81..0000000
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallListener.aidl
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import com.android.internal.telephony.IThirdPartyCallProvider;
-
-/**
- * Interface provided to ThirdPartyCallService. The service can use this to notify the listener of
- * changes to the call state.
- */
-oneway interface IThirdPartyCallListener {
- /**
- * Called by the service when a call provider is available to perform the outgoing or incoming
- * call.
- */
- void onCallProviderAttached(IThirdPartyCallProvider callProvider);
-
- /**
- * Notifies the listener that ringing has started for this call.
- */
- void onRingingStarted();
-
- /**
- * Notifies the listener that the call has been successfully established.
- */
- void onCallEstablished();
-
- /**
- * Notifies the listener that the call has ended.
- */
- void onCallEnded(int reason);
-}
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallProvider.aidl b/telephony/java/com/android/internal/telephony/IThirdPartyCallProvider.aidl
deleted file mode 100644
index 9d595b0..0000000
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallProvider.aidl
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import com.android.internal.telephony.IThirdPartyCallListener;
-import com.android.internal.telephony.IThirdPartyCallSendDtmfCallback;
-
-/**
- * Interface sent to ThirdPartyCallListener.onCallProviderAttached. This is used to control an
- * outgoing or incoming call.
- */
-oneway interface IThirdPartyCallProvider {
- /**
- * Mutes or unmutes the call.
- */
- void mute(boolean shouldMute);
-
- /**
- * Ends the current call. If this is an unanswered incoming call then the call is rejected (for
- * example, a notification is sent to a server that the user declined the call).
- */
- void hangup();
-
- /**
- * Accepts the incoming call.
- */
- void incomingCallAccept();
-
- /**
- * Sends the given DTMF code. The code can be '0'-'9', 'A'-'D', '#', or '*'.
- */
- void sendDtmf(char c, IThirdPartyCallSendDtmfCallback callback);
-}
diff --git a/telephony/java/com/android/internal/telephony/IThirdPartyCallService.aidl b/telephony/java/com/android/internal/telephony/IThirdPartyCallService.aidl
deleted file mode 100644
index 597567a..0000000
--- a/telephony/java/com/android/internal/telephony/IThirdPartyCallService.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import com.android.internal.telephony.IThirdPartyCallListener;
-
-/**
- * Interface provided by a service to start outgoing calls and attach to incoming calls.
- */
-oneway interface IThirdPartyCallService {
- /**
- * Call to start a new outgoing call.
- */
- void outgoingCallInitiate(IThirdPartyCallListener listener, String number);
-
- /**
- * Call to attach to an incoming call. This is in response to a call to
- * TelephonyManager.newIncomingThirdPartyCall.
- */
- void incomingCallAttach(IThirdPartyCallListener listener, String callId);
-}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index a54936b..2ebce9b 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -175,6 +175,11 @@
}
@Override
+ public File getNoBackupFilesDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public File getExternalFilesDir(String type) {
throw new UnsupportedOperationException();
}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index a14714a..e388480 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -31,6 +31,7 @@
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.KeySet;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -616,6 +617,26 @@
throw new UnsupportedOperationException();
}
+ @Override
+ public KeySet getKeySetByAlias(String packageName, String alias) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public KeySet getSigningKeySet(String packageName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSignedBy(String packageName, KeySet ks) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSignedByExactly(String packageName, KeySet ks) {
+ throw new UnsupportedOperationException();
+ }
+
/**
* @hide
*/
@@ -729,7 +750,13 @@
}
/** {@hide} */
- public PackageInstaller getPackageInstaller() {
+ public PackageInstaller getInstaller() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
+ public boolean isPackageAvailable(String packageName) {
throw new UnsupportedOperationException();
}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index 802f473..9cbb455 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -18,12 +18,14 @@
import android.media.MediaMetadata;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -43,7 +45,7 @@
protected MediaController.TransportControls mTransportControls;
private final Intent mServiceIntent;
- private Context mContext;
+ private Activity mContext;
private Listener mListener;
private SessionCallback mControllerCb;
private MediaSessionManager mManager;
@@ -51,7 +53,7 @@
private boolean mResumed;
- public PlayerController(Context context, Intent serviceIntent) {
+ public PlayerController(Activity context, Intent serviceIntent) {
mContext = context;
if (serviceIntent == null) {
mServiceIntent = new Intent(mContext, PlayerService.class);
@@ -140,6 +142,7 @@
mBinder = null;
mController = null;
mTransportControls = null;
+ mContext.setMediaController(null);
Log.d(TAG, "Disconnected from PlayerService");
if (mListener != null) {
@@ -151,12 +154,15 @@
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = IPlayerService.Stub.asInterface(service);
Log.d(TAG, "service is " + service + " binder is " + mBinder);
+ MediaSession.Token token;
try {
- mController = MediaController.fromToken(mBinder.getSessionToken());
+ token = mBinder.getSessionToken();
} catch (RemoteException e) {
Log.e(TAG, "Error getting session", e);
return;
}
+ mController = MediaController.fromToken(token);
+ mContext.setMediaController(mController);
mController.addCallback(mControllerCb, mHandler);
mTransportControls = mController.getTransportControls();
Log.d(TAG, "Ready to use PlayerService");
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index 7c0eabe..78353b2 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.support.media.protocols.MediaPlayerProtocol;
import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
+import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
@@ -59,9 +60,9 @@
mRenderer = new LocalRenderer(context, null);
mCallback = new SessionCb();
mRenderListener = new RenderListener();
- mPlaybackState = new PlaybackState();
- mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
- | PlaybackState.ACTION_PLAY);
+ PlaybackState.Builder psBob = new PlaybackState.Builder();
+ psBob.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY);
+ mPlaybackState = psBob.build();
mRenderer.registerListener(mRenderListener);
}
@@ -131,7 +132,10 @@
private void updateState(int newState) {
float rate = newState == PlaybackState.STATE_PLAYING ? 1 : 0;
long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
- mPlaybackState.setState(newState, position, rate);
+ PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+ bob.setState(newState, position, rate, SystemClock.elapsedRealtime());
+ bob.setErrorMessage(null);
+ mPlaybackState = bob.build();
mSession.setPlaybackState(mPlaybackState);
}
@@ -144,10 +148,12 @@
@Override
public void onError(int type, int extra, Bundle extras, Throwable error) {
Log.d(TAG, "Sending onError with type " + type + " and extra " + extra);
- mPlaybackState.setState(PlaybackState.STATE_ERROR, -1, 0);
+ PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+ bob.setState(PlaybackState.STATE_ERROR, -1, 0, 0);
if (error != null) {
- mPlaybackState.setErrorMessage(error.getLocalizedMessage());
+ bob.setErrorMessage(error.getLocalizedMessage());
}
+ mPlaybackState = bob.build();
mSession.setPlaybackState(mPlaybackState);
if (mListener != null) {
mListener.onPlayStateChanged(mPlaybackState);
@@ -156,36 +162,41 @@
@Override
public void onStateChanged(int newState) {
- if (newState != Renderer.STATE_ERROR) {
- mPlaybackState.setErrorMessage(null);
- }
long position = -1;
if (mRenderer != null) {
position = mRenderer.getSeekPosition();
}
+ int pbState;
+ float rate = 0;
+ String errorMsg = null;
switch (newState) {
case Renderer.STATE_ENDED:
case Renderer.STATE_STOPPED:
- mPlaybackState.setState(PlaybackState.STATE_STOPPED, position, 0);
+ pbState = PlaybackState.STATE_STOPPED;
break;
case Renderer.STATE_INIT:
case Renderer.STATE_PREPARING:
- mPlaybackState.setState(PlaybackState.STATE_BUFFERING, position, 0);
+ pbState = PlaybackState.STATE_BUFFERING;
break;
case Renderer.STATE_ERROR:
- mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0);
+ pbState = PlaybackState.STATE_ERROR;
break;
case Renderer.STATE_PAUSED:
- mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0);
+ pbState = PlaybackState.STATE_PAUSED;
break;
case Renderer.STATE_PLAYING:
- mPlaybackState.setState(PlaybackState.STATE_PLAYING, position, 1);
+ pbState = PlaybackState.STATE_PLAYING;
+ rate = 1;
break;
default:
- mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0);
- mPlaybackState.setErrorMessage("unkown state");
+ pbState = PlaybackState.STATE_ERROR;
+ errorMsg = "unknown state";
break;
}
+ PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+ bob.setState(pbState, position, rate, SystemClock.elapsedRealtime());
+ bob.setErrorMessage(errorMsg);
+ mPlaybackState = bob.build();
mSession.setPlaybackState(mPlaybackState);
if (mListener != null) {
mListener.onPlayStateChanged(mPlaybackState);
@@ -200,7 +211,10 @@
public void onFocusLost() {
Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
long position = mRenderer == null ? -1 : mRenderer.getSeekPosition();
- mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0);
+ PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+ bob.setState(PlaybackState.STATE_PAUSED, position, 0, SystemClock.elapsedRealtime());
+ bob.setErrorMessage(null);
+ mPlaybackState = bob.build();
mSession.setPlaybackState(mPlaybackState);
if (mListener != null) {
mListener.onPlayStateChanged(mPlaybackState);
diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
index 2e1478b..5845e48 100644
--- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
+++ b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java
@@ -29,6 +29,9 @@
import android.support.media.protocols.MediaPlayerProtocol;
import android.support.media.protocols.MediaPlayerProtocol.MediaInfo;
import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
import android.util.Log;
import com.android.onemedia.playback.LocalRenderer;
@@ -60,9 +63,9 @@
mHandler = new Handler();
mRenderer = new LocalRenderer(this, null);
mRenderListener = new RenderListener();
- mPlaybackState = new PlaybackState();
- mPlaybackState.setActions(PlaybackState.ACTION_PAUSE
- | PlaybackState.ACTION_PLAY);
+ PlaybackState.Builder bob = new PlaybackState.Builder();
+ bob.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY);
+ mPlaybackState = bob.build();
mRenderer.registerListener(mRenderListener);
}
@@ -178,36 +181,41 @@
@Override
public void onStateChanged(int newState) {
- if (newState != Renderer.STATE_ERROR) {
- mPlaybackState.setErrorMessage(null);
- }
long position = -1;
if (mRenderer != null) {
position = mRenderer.getSeekPosition();
}
+ int pbState;
+ float rate = 0;
+ String errorMsg = null;
switch (newState) {
case Renderer.STATE_ENDED:
case Renderer.STATE_STOPPED:
- mPlaybackState.setState(PlaybackState.STATE_STOPPED, position, 0);
+ pbState = PlaybackState.STATE_STOPPED;
break;
case Renderer.STATE_INIT:
case Renderer.STATE_PREPARING:
- mPlaybackState.setState(PlaybackState.STATE_BUFFERING, position, 0);
+ pbState = PlaybackState.STATE_BUFFERING;
break;
case Renderer.STATE_ERROR:
- mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0);
+ pbState = PlaybackState.STATE_ERROR;
break;
case Renderer.STATE_PAUSED:
- mPlaybackState.setState(PlaybackState.STATE_PAUSED, position, 0);
+ pbState = PlaybackState.STATE_PAUSED;
break;
case Renderer.STATE_PLAYING:
- mPlaybackState.setState(PlaybackState.STATE_PLAYING, position, 1);
+ pbState = PlaybackState.STATE_PLAYING;
+ rate = 1;
break;
default:
- mPlaybackState.setState(PlaybackState.STATE_ERROR, position, 0);
- mPlaybackState.setErrorMessage("unkown state");
+ pbState = PlaybackState.STATE_ERROR;
+ errorMsg = "unknown state";
break;
}
+ PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState);
+ bob.setState(pbState, position, rate, SystemClock.elapsedRealtime());
+ bob.setErrorMessage(errorMsg);
+ mPlaybackState = bob.build();
sendStatusUpdate(mPlaybackState.getState());
}
@@ -218,8 +226,9 @@
@Override
public void onFocusLost() {
- Log.d(TAG, "Focus lost, changing state to " + Renderer.STATE_PAUSED);
- mPlaybackState.setState(PlaybackState.STATE_PAUSED, mRenderer.getSeekPosition(), 0);
+ Log.d(TAG, "Focus lost, pausing");
+ // Don't update state here, we'll get a separate call to
+ // onStateChanged when it pauses
mRenderer.onPause();
}
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index a6c09f3..62c92a1 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -93,7 +93,7 @@
}
try {
- mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false);
+ mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false, false);
fail("IWindowManager.addAppToken did not throw SecurityException as"
+ " expected");
} catch (SecurityException e) {
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 12f5b92..28de933 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -77,6 +77,14 @@
int32_t layoutBoundsRight;
int32_t layoutBoundsBottom;
+ // Round rect outline description
+ int32_t outlineInsetsLeft;
+ int32_t outlineInsetsTop;
+ int32_t outlineInsetsRight;
+ int32_t outlineInsetsBottom;
+ float outlineRadius;
+ bool outlineFilled;
+
png_uint_32 allocHeight;
png_bytepp allocRows;
};
@@ -397,6 +405,98 @@
return NO_ERROR;
}
+static void find_max_opacity(png_byte** rows,
+ int startX, int startY, int endX, int endY, int dX, int dY,
+ int* out_inset)
+{
+ bool opaque_within_inset = true;
+ unsigned char max_opacity = 0;
+ int inset = 0;
+ *out_inset = 0;
+ for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+ png_byte* color = rows[y] + x * 4;
+ unsigned char opacity = color[3];
+ if (opacity > max_opacity) {
+ max_opacity = opacity;
+ *out_inset = inset;
+ }
+ if (opacity == 0xff) return;
+ }
+}
+
+static bool is_opaque_over_row(png_byte* row, int startX, int endX)
+{
+ for (int x = startX; x < endX; x++) {
+ png_byte* color = row + x * 4;
+ if (color[3] != 0xff) return false;
+ }
+ return true;
+}
+
+static bool is_opaque_over_col(png_byte** rows, int offsetX, int startY, int endY)
+{
+ for (int y = startY; y < endY; y++) {
+ png_byte* color = rows[y] + offsetX * 4;
+ if (color[3] != 0xff) return false;
+ }
+ return true;
+}
+
+static void get_outline(image_info* image)
+{
+ int midX = image->width / 2;
+ int midY = image->height / 2;
+ int endX = image->width - 2;
+ int endY = image->height - 2;
+
+ // find left and right extent of nine patch content on center row
+ if (image->width > 4) {
+ find_max_opacity(image->rows, 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
+ find_max_opacity(image->rows, endX, midY, midX, -1, -1, 0, &image->outlineInsetsRight);
+ } else {
+ image->outlineInsetsLeft = 0;
+ image->outlineInsetsRight = 0;
+ }
+
+ // find top and bottom extent of nine patch content on center column
+ if (image->height > 4) {
+ find_max_opacity(image->rows, midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
+ find_max_opacity(image->rows, midX, endY, -1, midY, 0, -1, &image->outlineInsetsBottom);
+ } else {
+ image->outlineInsetsTop = 0;
+ image->outlineInsetsBottom = 0;
+ }
+
+ int innerStartX = 1 + image->outlineInsetsLeft;
+ int innerStartY = 1 + image->outlineInsetsTop;
+ int innerEndX = endX - image->outlineInsetsRight;
+ int innerEndY = endY - image->outlineInsetsBottom;
+ int innerMidX = (innerEndX + innerStartX) / 2;
+ int innerMidY = (innerEndY + innerStartY) / 2;
+
+ // assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center
+ image->outlineFilled = is_opaque_over_row(image->rows[innerMidY], innerStartX, innerEndX)
+ && is_opaque_over_col(image->rows, innerMidX, innerStartY, innerStartY);
+
+ int diagonalInset = 0;
+ find_max_opacity(image->rows, innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
+ &diagonalInset);
+
+ // Determine source radius based upon inset
+ // radius = 1 / (sqrt(2) - 1) * inset
+ image->outlineRadius = 2.4142f * diagonalInset;
+
+ NOISY(printf("outline insets %d %d %d %d, rad %f, filled %d\n",
+ image->outlineFilled,
+ image->outlineInsetsLeft,
+ image->outlineInsetsTop,
+ image->outlineInsetsRight,
+ image->outlineInsetsBottom,
+ image->outlineRadius,
+ image->outlineFilled));
+}
+
static uint32_t get_color(
png_bytepp rows, int left, int top, int right, int bottom)
@@ -571,6 +671,9 @@
image->layoutBoundsRight, image->layoutBoundsBottom));
}
+ // use opacity of pixels to estimate the round rect outline
+ get_outline(image);
+
// If padding is not yet specified, take values from size.
if (image->info9Patch.paddingLeft < 0) {
image->info9Patch.paddingLeft = xDivs[0];
@@ -966,9 +1069,10 @@
int bit_depth, interlace_type, compression_type;
int i;
- png_unknown_chunk unknowns[2];
+ png_unknown_chunk unknowns[3];
unknowns[0].data = NULL;
unknowns[1].data = NULL;
+ unknowns[2].data = NULL;
png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep));
if (outRows == (png_bytepp) 0) {
@@ -1038,12 +1142,17 @@
}
if (imageInfo.is9Patch) {
- int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0);
- int p_index = imageInfo.haveLayoutBounds ? 1 : 0;
- int b_index = 0;
+ int chunk_count = 2 + (imageInfo.haveLayoutBounds ? 1 : 0);
+ int p_index = imageInfo.haveLayoutBounds ? 2 : 1;
+ int b_index = 1;
+ int o_index = 0;
+
+ // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
png_byte *chunk_names = imageInfo.haveLayoutBounds
- ? (png_byte*)"npLb\0npTc\0"
- : (png_byte*)"npTc";
+ ? (png_byte*)"npOl\0npLb\0npTc\0"
+ : (png_byte*)"npOl\0npTc";
+
+ // base 9 patch data
NOISY(printf("Adding 9-patch info...\n"));
strcpy((char*)unknowns[p_index].name, "npTc");
unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
@@ -1051,6 +1160,18 @@
// TODO: remove the check below when everything works
checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data);
+ // automatically generated 9 patch outline data
+ int chunk_size = sizeof(png_uint_32) * 6;
+ strcpy((char*)unknowns[o_index].name, "npOl");
+ unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1);
+ png_byte outputData[chunk_size];
+ memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32));
+ ((float*) outputData)[4] = imageInfo.outlineRadius;
+ ((png_uint_32*) outputData)[5] = imageInfo.outlineFilled ? 1 : 0;
+ memcpy(unknowns[o_index].data, &outputData, chunk_size);
+ unknowns[o_index].size = chunk_size;
+
+ // optional optical inset / layout bounds data
if (imageInfo.haveLayoutBounds) {
int chunk_size = sizeof(png_uint_32) * 4;
strcpy((char*)unknowns[b_index].name, "npLb");
@@ -1099,6 +1220,7 @@
free(outRows);
free(unknowns[0].data);
free(unknowns[1].data);
+ free(unknowns[2].data);
png_get_IHDR(write_ptr, write_info, &width, &height,
&bit_depth, &color_type, &interlace_type,
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 1942831..ead3b13 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -61,7 +61,7 @@
$(hide) mkdir -p $(dir $@)
$(hide) rm -f $@
$(hide) ls -l $(built_framework_classes)
- $(hide) java -jar $(built_layoutlib_create_jar) \
+ $(hide) java -ea -jar $(built_layoutlib_create_jar) \
$@ \
$(built_core_classes) \
$(built_framework_classes) \
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 2f40003..6927b26 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -72,7 +72,7 @@
@Override
public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
- boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9)
+ boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10)
throws RemoteException {
// TODO Auto-generated method stub
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
similarity index 60%
rename from tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
rename to tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
index 112250d..607e628 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
@@ -14,24 +14,23 @@
* limitations under the License.
*/
-package android.content.res;
+package com.android.layoutlib.bridge.android;
import java.util.Locale;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import com.ibm.icu.util.ULocale;
/**
- * Delegate used to provide new implementation of a select few methods of {@link Resources}
+ * This class provides an alternate implementation for {@code java.util.Locale#toLanguageTag}
+ * which is only available after Java 6.
*
- * Through the layoutlib_create tool, the original methods of Resources have been replaced
- * by calls to methods of the same name in this delegate class.
- *
+ * The create tool re-writes references to the above mentioned method to this one. Hence it's
+ * imperative that this class is not deleted unless the create tool is modified.
*/
-public class Resources_Delegate {
+@SuppressWarnings("UnusedDeclaration")
+public class AndroidLocale {
- @LayoutlibDelegate
- /*package*/ static String localeToLanguageTag(Resources res, Locale locale) {
+ public static String toLanguageTag(Locale locale) {
return ULocale.forLocale(locale).toLanguageTag();
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index efd55bf..ca61ffb 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1083,6 +1083,12 @@
}
@Override
+ public File getNoBackupFilesDir() {
+ // pass
+ return null;
+ }
+
+ @Override
public File getExternalFilesDir(String type) {
// pass
return null;
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index 8de64db..727b194 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -39,7 +39,8 @@
The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the configuration
is done in the main() method and the CreateInfo structure is expected to change with the Android
-platform as new classes are added, changed or removed.
+platform as new classes are added, changed or removed. Some configuration that may be platform
+dependent is also present elsewhere in code.
The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the platform, that
provides all the necessary missing implementation for rendering graphics in Eclipse.
@@ -95,7 +96,7 @@
- specific classes to refactor.
Each of these are specific strategies we use to be able to modify the Android code to fit within the
-Eclipse renderer. These strategies are explained beow.
+Eclipse renderer. These strategies are explained below.
The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it
to produce a byte array suitable for the final JAR file.
@@ -130,9 +131,11 @@
valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on
Mac has horrible font rendering support.
-ReplaceMethodCallsAdapter replaces calls to certain methods. Currently, it only rewrites calls to
-specialized versions of java.lang.System.arraycopy(), which are not part of the Desktop VM to call
-the more general method java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V.
+ReplaceMethodCallsAdapter replaces calls to certain methods. This is different from the
+DelegateMethodAdapter since it doesn't preserve the original copy of the method and more importantly
+changes the calls to a method in each class instead of changing the implementation of the method.
+This is useful for methods in the Java namespace where we cannot add delegates. The configuration
+for this is not done through the CreateInfo class, but done in the ReplaceMethodAdapter.
The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7
Transformation chains in the asm user guide, link in the References.) The order of execution of
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index 767e597..e043d4d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -724,9 +724,8 @@
considerDesc(desc);
- // Check if method is a specialized version of java.lang.System.arrayCopy()
- if (owner.equals("java/lang/System") && name.equals("arraycopy")
- && !desc.equals("(Ljava/lang/Object;ILjava/lang/Object;II)V")) {
+ // Check if method needs to replaced by a call to a different method.
+ if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) {
mReplaceMethodCallClasses.add(mOwnerClass);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 552fb6c..8fb8928 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -126,7 +126,6 @@
"android.content.res.Resources$Theme#obtainStyledAttributes",
"android.content.res.Resources$Theme#resolveAttribute",
"android.content.res.Resources$Theme#resolveAttributes",
- "android.content.res.Resources#localeToLanguageTag",
"android.content.res.AssetManager#newTheme",
"android.content.res.AssetManager#deleteTheme",
"android.content.res.AssetManager#applyThemeStyle",
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index ae17417..0b5fb46 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -20,12 +20,15 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
- * Replaces calls to certain methods that do not exist in the Desktop VM.
+ * Replaces calls to certain methods that do not exist in the Desktop VM. Useful for methods in the
+ * "java" package.
*/
public class ReplaceMethodCallsAdapter extends ClassVisitor {
@@ -37,6 +40,60 @@
"([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
"([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
+ private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(2);
+
+ // Static initialization block to initialize METHOD_REPLACERS.
+ static {
+ // Case 1: java.lang.System.arraycopy()
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc) {
+ return owner.equals("java/lang/System") && name.equals("arraycopy") &&
+ ARRAYCOPY_DESCRIPTORS.contains(desc);
+ }
+
+ @Override
+ public void replace(int opcode, String owner, String name, String desc,
+ int[] opcodeOut, String[] output) {
+ assert isNeeded(owner, name, desc) && output.length == 3
+ && opcodeOut.length == 1;
+ opcodeOut[0] = opcode;
+ output[0] = owner;
+ output[1] = name;
+ output[2] = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
+ }
+ });
+
+ // Case 2: java.util.Locale.toLanguageTag()
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc) {
+ return owner.equals("java/util/Locale") && name.equals("toLanguageTag") &&
+ "()Ljava/lang/String;".equals(desc);
+ }
+
+ @Override
+ public void replace(int opcode, String owner, String name, String desc,
+ int[] opcodeOut, String[] output) {
+ assert isNeeded(owner, name, desc) && output.length == 3
+ && opcodeOut.length == 1;
+ opcodeOut[0] = Opcodes.INVOKESTATIC;
+ output[0] = "com.android.layoutlib.bridge.android.AndroidLocale";
+ output[1] = name;
+ output[2] = "(Ljava/util/Locale;)Ljava/lang/String;";
+ }
+ });
+ }
+
+ public static boolean isReplacementNeeded(String owner, String name, String desc) {
+ for (MethodReplacer replacer : METHOD_REPLACERS) {
+ if (replacer.isNeeded(owner, name, desc)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public ReplaceMethodCallsAdapter(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
@@ -56,13 +113,34 @@
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
// Check if method is a specialized version of java.lang.System.arrayCopy
- if (owner.equals("java/lang/System") && name.equals("arraycopy")) {
-
- if (ARRAYCOPY_DESCRIPTORS.contains(desc)) {
- desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
+ for (MethodReplacer replacer : METHOD_REPLACERS) {
+ if (replacer.isNeeded(owner, name, desc)) {
+ String[] output = new String[3];
+ int[] opcodeOut = new int[1];
+ replacer.replace(opcode, owner, name, desc, opcodeOut, output);
+ opcode = opcodeOut[0];
+ owner = output[0];
+ name = output[1];
+ desc = output[2];
+ break;
}
}
super.visitMethodInsn(opcode, owner, name, desc);
}
}
+
+ private interface MethodReplacer {
+ public boolean isNeeded(String owner, String name, String desc);
+
+ /**
+ * This method must update the values of the output arrays with the new values of method
+ * attributes - opcode, owner, name and desc.
+ * @param opcodeOut An array that will contain the new value of the opcode. The size of
+ * the array must be 1.
+ * @param output An array that will contain the new values of the owner, name and desc in
+ * that order. The size of the array must be 3.
+ */
+ public void replace(int opcode, String owner, String name, String desc, int[] opcodeOut,
+ String[] output);
+ }
}