diff options
140 files changed, 5435 insertions, 1893 deletions
diff --git a/api/current.txt b/api/current.txt index 94e07fc684ab..07a4f6d117d9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -71,6 +71,7 @@ package android { field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED"; field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE"; + field public static final java.lang.String GET_PASSWORD_PRIVILEGED = "android.permission.GET_PASSWORD_PRIVILEGED"; field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS"; field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH"; field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER"; @@ -5900,6 +5901,7 @@ package android.app.admin { method public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(java.lang.String); + method public boolean isManagedProfile(android.content.ComponentName); method public boolean isMasterVolumeMuted(android.content.ComponentName); method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isProfileOwnerApp(java.lang.String); @@ -9915,6 +9917,7 @@ package android.content.pm { field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80 field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400 field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10 + field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800 field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10 field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200 field public static final int PROTECTION_MASK_BASE = 15; // 0xf @@ -19809,7 +19812,7 @@ package android.media { public static abstract class AudioManager.AudioRecordingCallback { ctor public AudioManager.AudioRecordingCallback(); - method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]); + method public void onRecordingConfigChanged(android.media.AudioRecordingConfiguration[]); } public static abstract interface AudioManager.OnAudioFocusChangeListener { @@ -20578,12 +20581,13 @@ package android.media { field public static final int DolbyVisionLevelUhd30 = 64; // 0x40 field public static final int DolbyVisionLevelUhd48 = 128; // 0x80 field public static final int DolbyVisionLevelUhd60 = 256; // 0x100 - field public static final int DolbyVisionProfileDvavDen = 2; // 0x2 - field public static final int DolbyVisionProfileDvavDer = 1; // 0x1 - field public static final int DolbyVisionProfileDvheDen = 4; // 0x4 - field public static final int DolbyVisionProfileDvheDer = 3; // 0x3 - field public static final int DolbyVisionProfileDvheDtr = 5; // 0x5 - field public static final int DolbyVisionProfileDvheStn = 6; // 0x6 + field public static final int DolbyVisionProfileDvavPen = 2; // 0x2 + field public static final int DolbyVisionProfileDvavPer = 1; // 0x1 + field public static final int DolbyVisionProfileDvheDen = 8; // 0x8 + field public static final int DolbyVisionProfileDvheDer = 4; // 0x4 + field public static final int DolbyVisionProfileDvheDth = 64; // 0x40 + field public static final int DolbyVisionProfileDvheDtr = 16; // 0x10 + field public static final int DolbyVisionProfileDvheStn = 32; // 0x20 field public static final int H263Level10 = 1; // 0x1 field public static final int H263Level20 = 2; // 0x2 field public static final int H263Level30 = 4; // 0x4 @@ -36726,6 +36730,7 @@ package android.telephony { field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string"; field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool"; + field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool"; field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int"; field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; diff --git a/api/system-current.txt b/api/system-current.txt index 69a49d15144b..fe562bb9014f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -103,6 +103,7 @@ package android { field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; field public static final java.lang.String GET_PACKAGE_IMPORTANCE = "android.permission.GET_PACKAGE_IMPORTANCE"; field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE"; + field public static final java.lang.String GET_PASSWORD_PRIVILEGED = "android.permission.GET_PASSWORD_PRIVILEGED"; field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"; field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS"; field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO"; @@ -6047,6 +6048,7 @@ package android.app.admin { method public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(java.lang.String); + method public boolean isManagedProfile(android.content.ComponentName); method public boolean isMasterVolumeMuted(android.content.ComponentName); method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isProfileOwnerApp(java.lang.String); @@ -10314,6 +10316,7 @@ package android.content.pm { field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80 field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400 field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10 + field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800 field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10 field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200 field public static final int PROTECTION_MASK_BASE = 15; // 0xf @@ -15268,6 +15271,7 @@ package android.hardware.location { ctor public ContextHubInfo(); method public int describeContents(); method public int getId(); + method public int getMaxPacketLengthBytes(); method public android.hardware.location.MemoryRegion[] getMemoryRegions(); method public java.lang.String getName(); method public float getPeakMips(); @@ -15295,10 +15299,6 @@ package android.hardware.location { method public int sendMessage(int, int, android.hardware.location.ContextHubMessage); method public int unloadNanoApp(int); method public int unregisterCallback(android.hardware.location.ContextHubManager.Callback); - field public static final int ANY_HUB = -1; // 0xffffffff - field public static final int MSG_DATA_SEND = 3; // 0x3 - field public static final int MSG_LOAD_NANO_APP = 1; // 0x1 - field public static final int MSG_UNLOAD_NANO_APP = 2; // 0x2 } public static abstract class ContextHubManager.Callback { @@ -15492,7 +15492,7 @@ package android.hardware.location { public class NanoAppInstanceInfo { ctor public NanoAppInstanceInfo(); method public int describeContents(); - method public int getAppId(); + method public long getAppId(); method public int getAppVersion(); method public int getContexthubId(); method public int getHandle(); @@ -15503,17 +15503,6 @@ package android.hardware.location { method public int getNeededWriteMemBytes(); method public int[] getOutputEvents(); method public java.lang.String getPublisher(); - method public void setAppId(int); - method public void setAppVersion(int); - method public void setContexthubId(int); - method public void setHandle(int); - method public void setName(java.lang.String); - method public void setNeededExecMemBytes(int); - method public void setNeededReadMemBytes(int); - method public void setNeededSensors(int[]); - method public void setNeededWriteMemBytes(int); - method public void setOutputEvents(int[]); - method public void setPublisher(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppInstanceInfo> CREATOR; } @@ -21299,7 +21288,7 @@ package android.media { public static abstract class AudioManager.AudioRecordingCallback { ctor public AudioManager.AudioRecordingCallback(); - method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]); + method public void onRecordingConfigChanged(android.media.AudioRecordingConfiguration[]); } public static abstract interface AudioManager.OnAudioFocusChangeListener { @@ -22071,12 +22060,13 @@ package android.media { field public static final int DolbyVisionLevelUhd30 = 64; // 0x40 field public static final int DolbyVisionLevelUhd48 = 128; // 0x80 field public static final int DolbyVisionLevelUhd60 = 256; // 0x100 - field public static final int DolbyVisionProfileDvavDen = 2; // 0x2 - field public static final int DolbyVisionProfileDvavDer = 1; // 0x1 - field public static final int DolbyVisionProfileDvheDen = 4; // 0x4 - field public static final int DolbyVisionProfileDvheDer = 3; // 0x3 - field public static final int DolbyVisionProfileDvheDtr = 5; // 0x5 - field public static final int DolbyVisionProfileDvheStn = 6; // 0x6 + field public static final int DolbyVisionProfileDvavPen = 2; // 0x2 + field public static final int DolbyVisionProfileDvavPer = 1; // 0x1 + field public static final int DolbyVisionProfileDvheDen = 8; // 0x8 + field public static final int DolbyVisionProfileDvheDer = 4; // 0x4 + field public static final int DolbyVisionProfileDvheDth = 64; // 0x40 + field public static final int DolbyVisionProfileDvheDtr = 16; // 0x10 + field public static final int DolbyVisionProfileDvheStn = 32; // 0x20 field public static final int H263Level10 = 1; // 0x1 field public static final int H263Level20 = 2; // 0x2 field public static final int H263Level30 = 4; // 0x4 @@ -24628,6 +24618,7 @@ package android.media.tv { method public android.media.tv.TvInputInfo.Builder setHdmiDeviceInfo(android.hardware.hdmi.HdmiDeviceInfo); method public android.media.tv.TvInputInfo.Builder setIcon(android.graphics.drawable.Icon); method public android.media.tv.TvInputInfo.Builder setIcon(android.graphics.drawable.Icon, int); + method public android.media.tv.TvInputInfo.Builder setLabel(java.lang.CharSequence); method public android.media.tv.TvInputInfo.Builder setLabel(int); method public android.media.tv.TvInputInfo.Builder setParentId(java.lang.String); method public android.media.tv.TvInputInfo.Builder setTunerCount(int); @@ -31331,8 +31322,8 @@ package android.os { field public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1; // 0x1 field public static final deprecated int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa field public static final deprecated int SCREEN_DIM_WAKE_LOCK = 6; // 0x6 - field public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; // 0x3 field public static final int SUSTAINED_PERFORMANCE_WAKE_LOCK = 256; // 0x100 + field public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; // 0x3 field public static final int USER_ACTIVITY_EVENT_BUTTON = 1; // 0x1 field public static final int USER_ACTIVITY_EVENT_OTHER = 0; // 0x0 field public static final int USER_ACTIVITY_EVENT_TOUCH = 2; // 0x2 @@ -32675,7 +32666,6 @@ package android.printservice.recommendation { method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onConnected(); method public abstract void onDisconnected(); - method public final boolean onUnbind(android.content.Intent); method public final void updateRecommendations(java.util.List<android.printservice.recommendation.RecommendationInfo>); } @@ -39416,6 +39406,7 @@ package android.telephony { field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string"; field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool"; + field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool"; field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int"; field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; @@ -48859,6 +48850,24 @@ package android.webkit { method public abstract boolean shouldDelayChildPressedState(); } + public final class WebViewProviderInfo implements android.os.Parcelable { + ctor public WebViewProviderInfo(java.lang.String, java.lang.String, boolean, boolean, java.lang.String[]); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.webkit.WebViewProviderInfo> CREATOR; + field public final boolean availableByDefault; + field public final java.lang.String description; + field public final boolean isFallback; + field public final java.lang.String packageName; + field public final java.lang.String[] signatures; + } + + public final class WebViewUpdateService { + method public static android.webkit.WebViewProviderInfo[] getAllWebViewPackages(); + method public static java.lang.String getCurrentWebViewPackageName(); + method public static android.webkit.WebViewProviderInfo[] getValidWebViewPackages(); + } + } package android.widget { diff --git a/api/test-current.txt b/api/test-current.txt index 53da4598c02b..09c55b927c60 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -71,6 +71,7 @@ package android { field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED"; field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE"; + field public static final java.lang.String GET_PASSWORD_PRIVILEGED = "android.permission.GET_PASSWORD_PRIVILEGED"; field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS"; field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH"; field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER"; @@ -5904,6 +5905,7 @@ package android.app.admin { method public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(java.lang.String); + method public boolean isManagedProfile(android.content.ComponentName); method public boolean isMasterVolumeMuted(android.content.ComponentName); method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isProfileOwnerApp(java.lang.String); @@ -9925,6 +9927,7 @@ package android.content.pm { field public static final int PROTECTION_FLAG_PRE23 = 128; // 0x80 field public static final int PROTECTION_FLAG_PREINSTALLED = 1024; // 0x400 field public static final int PROTECTION_FLAG_PRIVILEGED = 16; // 0x10 + field public static final int PROTECTION_FLAG_SETUP = 2048; // 0x800 field public static final deprecated int PROTECTION_FLAG_SYSTEM = 16; // 0x10 field public static final int PROTECTION_FLAG_VERIFIER = 512; // 0x200 field public static final int PROTECTION_MASK_BASE = 15; // 0xf @@ -19874,7 +19877,7 @@ package android.media { public static abstract class AudioManager.AudioRecordingCallback { ctor public AudioManager.AudioRecordingCallback(); - method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]); + method public void onRecordingConfigChanged(android.media.AudioRecordingConfiguration[]); } public static abstract interface AudioManager.OnAudioFocusChangeListener { @@ -20643,12 +20646,13 @@ package android.media { field public static final int DolbyVisionLevelUhd30 = 64; // 0x40 field public static final int DolbyVisionLevelUhd48 = 128; // 0x80 field public static final int DolbyVisionLevelUhd60 = 256; // 0x100 - field public static final int DolbyVisionProfileDvavDen = 2; // 0x2 - field public static final int DolbyVisionProfileDvavDer = 1; // 0x1 - field public static final int DolbyVisionProfileDvheDen = 4; // 0x4 - field public static final int DolbyVisionProfileDvheDer = 3; // 0x3 - field public static final int DolbyVisionProfileDvheDtr = 5; // 0x5 - field public static final int DolbyVisionProfileDvheStn = 6; // 0x6 + field public static final int DolbyVisionProfileDvavPen = 2; // 0x2 + field public static final int DolbyVisionProfileDvavPer = 1; // 0x1 + field public static final int DolbyVisionProfileDvheDen = 8; // 0x8 + field public static final int DolbyVisionProfileDvheDer = 4; // 0x4 + field public static final int DolbyVisionProfileDvheDth = 64; // 0x40 + field public static final int DolbyVisionProfileDvheDtr = 16; // 0x10 + field public static final int DolbyVisionProfileDvheStn = 32; // 0x20 field public static final int H263Level10 = 1; // 0x1 field public static final int H263Level20 = 2; // 0x2 field public static final int H263Level30 = 4; // 0x4 @@ -36798,6 +36802,7 @@ package android.telephony { field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string"; field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool"; + field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool"; field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int"; field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index e520b406656b..7465ed92e469 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -2798,6 +2798,15 @@ public class AccountManager { if (account == null) { throw new IllegalArgumentException("account is null"); } + + // Always include the calling package name. This just makes life easier + // down stream. + final Bundle optionsIn = new Bundle(); + if (options != null) { + optionsIn.putAll(options); + } + optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); + return new AmsTask(activity, handler, callback) { @Override public void doWork() throws RemoteException { @@ -2806,7 +2815,7 @@ public class AccountManager { account, authTokenType, activity != null, - options); + optionsIn); } }.start(); } diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java index d3ca7eebdfcd..4a1aff74ddf6 100644 --- a/core/java/android/app/ActivityTransitionState.java +++ b/core/java/android/app/ActivityTransitionState.java @@ -239,9 +239,6 @@ class ActivityTransitionState { public void onResume(Activity activity, boolean isTopOfTask) { // After orientation change, the onResume can come in before the top Activity has // left, so if the Activity is not top, wait a second for the top Activity to exit. - if (mCalledExitCoordinator == null) { - return; // This is the called activity - } if (isTopOfTask || mEnterTransitionCoordinator == null) { restoreExitedViews(); restoreReenteringViews(); diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index cb2130c4528e..e4fff9dc8a8c 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -905,10 +905,12 @@ public class AlarmManager { ListenerWrapper wrapper = null; synchronized (AlarmManager.class) { - final WeakReference<ListenerWrapper> wrapperRef; - wrapperRef = sWrappers.get(listener); - if (wrapperRef != null) { - wrapper = wrapperRef.get(); + if (sWrappers != null) { + final WeakReference<ListenerWrapper> wrapperRef; + wrapperRef = sWrappers.get(listener); + if (wrapperRef != null) { + wrapper = wrapperRef.get(); + } } } diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java index 83dc506371cf..bd55a06c40f5 100644 --- a/core/java/android/app/DatePickerDialog.java +++ b/core/java/android/app/DatePickerDialog.java @@ -58,7 +58,7 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, * @param context the parent context */ public DatePickerDialog(@NonNull Context context) { - this(context, 0); + this(context, 0, null, Calendar.getInstance(), -1, -1, -1); } /** @@ -70,24 +70,7 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, * {@code context}'s default alert dialog theme */ public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId) { - super(context, resolveDialogTheme(context, themeResId)); - - final Context themeContext = getContext(); - final LayoutInflater inflater = LayoutInflater.from(themeContext); - final View view = inflater.inflate(R.layout.date_picker_dialog, null); - setView(view); - - setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this); - setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this); - setButtonPanelLayoutHint(LAYOUT_HINT_SIDE); - - final Calendar calendar = Calendar.getInstance(); - final int year = calendar.get(Calendar.YEAR); - final int monthOfYear = calendar.get(Calendar.MONTH); - final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); - mDatePicker = (DatePicker) view.findViewById(R.id.datePicker); - mDatePicker.init(year, monthOfYear, dayOfMonth, this); - mDatePicker.setValidationCallback(mValidationCallback); + this(context, themeResId, null, Calendar.getInstance(), -1, -1, -1); } /** @@ -104,7 +87,7 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, */ public DatePickerDialog(@NonNull Context context, @Nullable OnDateSetListener listener, int year, int month, int dayOfMonth) { - this(context, 0, listener, year, month, dayOfMonth); + this(context, 0, listener, null, year, month, dayOfMonth); } /** @@ -116,16 +99,40 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, * {@code context}'s default alert dialog theme * @param listener the listener to call when the user sets the date * @param year the initially selected year - * @param month the initially selected month (0-11 for compatibility with - * {@link Calendar#MONTH}) + * @param monthOfYear the initially selected month of the year (0-11 for + * compatibility with {@link Calendar#MONTH}) * @param dayOfMonth the initially selected day of month (1-31, depending * on month) */ public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId, - @Nullable OnDateSetListener listener, int year, int month, int dayOfMonth) { - this(context, themeResId); + @Nullable OnDateSetListener listener, int year, int monthOfYear, int dayOfMonth) { + this(context, themeResId, listener, null, year, monthOfYear, dayOfMonth); + } + + private DatePickerDialog(@NonNull Context context, @StyleRes int themeResId, + @Nullable OnDateSetListener listener, @Nullable Calendar calendar, int year, + int monthOfYear, int dayOfMonth) { + super(context, resolveDialogTheme(context, themeResId)); + + final Context themeContext = getContext(); + final LayoutInflater inflater = LayoutInflater.from(themeContext); + final View view = inflater.inflate(R.layout.date_picker_dialog, null); + setView(view); + + setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this); + setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this); + setButtonPanelLayoutHint(LAYOUT_HINT_SIDE); + + if (calendar != null) { + year = calendar.get(Calendar.YEAR); + monthOfYear = calendar.get(Calendar.MONTH); + dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + } + + mDatePicker = (DatePicker) view.findViewById(R.id.datePicker); + mDatePicker.init(year, monthOfYear, dayOfMonth, this); + mDatePicker.setValidationCallback(mValidationCallback); - mDatePicker.updateDate(year, month, dayOfMonth); mDateSetListener = listener; } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 0bb1097461fd..85a0403fe57e 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -16,6 +16,10 @@ package android.app; +import com.android.internal.R; +import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.policy.PhoneWindow; + import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.IdRes; @@ -43,7 +47,6 @@ import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.KeyEvent; -import android.view.KeyboardShortcutGroup; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -57,12 +60,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; -import com.android.internal.R; -import com.android.internal.app.WindowDecorActionBar; -import com.android.internal.policy.PhoneWindow; - import java.lang.ref.WeakReference; -import java.util.List; /** * Base class for Dialogs. @@ -94,11 +92,14 @@ public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { private static final String TAG = "Dialog"; private Activity mOwnerActivity; - + + private final WindowManager mWindowManager; + final Context mContext; - final WindowManager mWindowManager; - Window mWindow; + final Window mWindow; + View mDecor; + private ActionBar mActionBar; /** * This field should be made private, so it is hidden from the SDK. @@ -123,7 +124,7 @@ public class Dialog implements DialogInterface, Window.Callback, private static final int CANCEL = 0x44; private static final int SHOW = 0x45; - private Handler mListenersHandler; + private final Handler mListenersHandler; private SearchEvent mSearchEvent; @@ -131,11 +132,7 @@ public class Dialog implements DialogInterface, Window.Callback, private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY; - private final Runnable mDismissAction = new Runnable() { - public void run() { - dismissDialog(); - } - }; + private final Runnable mDismissAction = this::dismissDialog; /** * Creates a dialog window that uses the default dialog theme. @@ -198,14 +195,15 @@ public class Dialog implements DialogInterface, Window.Callback, * @hide */ @Deprecated - protected Dialog(@NonNull Context context, boolean cancelable, Message cancelCallback) { + protected Dialog(@NonNull Context context, boolean cancelable, + @Nullable Message cancelCallback) { this(context); mCancelable = cancelable; mCancelMessage = cancelCallback; } protected Dialog(@NonNull Context context, boolean cancelable, - OnCancelListener cancelListener) { + @Nullable OnCancelListener cancelListener) { this(context); mCancelable = cancelable; setOnCancelListener(cancelListener); @@ -216,8 +214,7 @@ public class Dialog implements DialogInterface, Window.Callback, * * @return Context The Context used by the Dialog. */ - @NonNull - public final Context getContext() { + public final @NonNull Context getContext() { return mContext; } @@ -226,7 +223,7 @@ public class Dialog implements DialogInterface, Window.Callback, * * @return The ActionBar attached to the dialog or null if no ActionBar is present. */ - public ActionBar getActionBar() { + public @Nullable ActionBar getActionBar() { return mActionBar; } @@ -236,7 +233,7 @@ public class Dialog implements DialogInterface, Window.Callback, * * @param activity The Activity that owns this dialog. */ - public final void setOwnerActivity(Activity activity) { + public final void setOwnerActivity(@NonNull Activity activity) { mOwnerActivity = activity; getWindow().setVolumeControlStream(mOwnerActivity.getVolumeControlStream()); @@ -250,7 +247,7 @@ public class Dialog implements DialogInterface, Window.Callback, * * @return The Activity that owns this Dialog. */ - public final Activity getOwnerActivity() { + public final @Nullable Activity getOwnerActivity() { return mOwnerActivity; } @@ -316,13 +313,10 @@ public class Dialog implements DialogInterface, Window.Callback, l = nl; } - try { - mWindowManager.addView(mDecor, l); - mShowing = true; - - sendShowMessage(); - } finally { - } + mWindowManager.addView(mDecor, l); + mShowing = true; + + sendShowMessage(); } /** @@ -388,7 +382,7 @@ public class Dialog implements DialogInterface, Window.Callback, } } - // internal method to make sure mcreated is set properly without requiring + // internal method to make sure mCreated is set properly without requiring // users to call through to super in onCreate void dispatchOnCreate(Bundle savedInstanceState) { if (!mCreated) { @@ -400,7 +394,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * Similar to {@link Activity#onCreate}, you should initialize your dialog * in this method, including calling {@link #setContentView}. - * @param savedInstanceState If this dialog is being reinitalized after a + * @param savedInstanceState If this dialog is being reinitialized after a * the hosting activity was previously shut down, holds the result from * the most recent call to {@link #onSaveInstanceState}, or null if this * is the first time. @@ -433,7 +427,7 @@ public class Dialog implements DialogInterface, Window.Callback, * state. * @return A bundle with the state of the dialog. */ - public Bundle onSaveInstanceState() { + public @NonNull Bundle onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing); if (mCreated) { @@ -452,7 +446,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @param savedInstanceState The state of the dialog previously saved by * {@link #onSaveInstanceState()}. */ - public void onRestoreInstanceState(Bundle savedInstanceState) { + public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG); if (dialogHierarchyState == null) { // dialog has never been shown, or onCreated, nothing to restore. @@ -473,7 +467,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @return Window The current window, or null if the activity is not * visual. */ - public Window getWindow() { + public @Nullable Window getWindow() { return mWindow; } @@ -486,7 +480,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @see #getWindow * @see android.view.Window#getCurrentFocus */ - public View getCurrentFocus() { + public @Nullable View getCurrentFocus() { return mWindow != null ? mWindow.getCurrentFocus() : null; } @@ -498,8 +492,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @param id the identifier of the view to find * @return The view with the given id or null. */ - @Nullable - public View findViewById(@IdRes int id) { + public @Nullable View findViewById(@IdRes int id) { return mWindow.findViewById(id); } @@ -520,19 +513,19 @@ public class Dialog implements DialogInterface, Window.Callback, * * @param view The desired content to display. */ - public void setContentView(View view) { + public void setContentView(@NonNull View view) { mWindow.setContentView(view); } /** * Set the screen content to an explicit view. This view is placed * directly into the screen's view hierarchy. It can itself be a complex - * view hierarhcy. + * view hierarchy. * * @param view The desired content to display. * @param params Layout parameters for the view. */ - public void setContentView(View view, ViewGroup.LayoutParams params) { + public void setContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) { mWindow.setContentView(view, params); } @@ -543,7 +536,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @param view The desired content to display. * @param params Layout parameters for the view. */ - public void addContentView(View view, ViewGroup.LayoutParams params) { + public void addContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) { mWindow.addContentView(view, params); } @@ -552,7 +545,7 @@ public class Dialog implements DialogInterface, Window.Callback, * * @param title The new text to display in the title. */ - public void setTitle(CharSequence title) { + public void setTitle(@Nullable CharSequence title) { mWindow.setTitle(title); mWindow.getAttributes().setTitle(title); } @@ -578,7 +571,8 @@ public class Dialog implements DialogInterface, Window.Callback, * @see #onKeyUp * @see android.view.KeyEvent */ - public boolean onKeyDown(int keyCode, KeyEvent event) { + @Override + public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { event.startTracking(); return true; @@ -592,7 +586,8 @@ public class Dialog implements DialogInterface, Window.Callback, * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle * the event). */ - public boolean onKeyLongPress(int keyCode, KeyEvent event) { + @Override + public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) { return false; } @@ -605,7 +600,8 @@ public class Dialog implements DialogInterface, Window.Callback, * @see #onKeyDown * @see KeyEvent */ - public boolean onKeyUp(int keyCode, KeyEvent event) { + @Override + public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) { onBackPressed(); @@ -619,7 +615,8 @@ public class Dialog implements DialogInterface, Window.Callback, * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle * the event). */ - public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, @NonNull KeyEvent event) { return false; } @@ -644,7 +641,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @param event Description of the key event. * @return True if the key shortcut was handled. */ - public boolean onKeyShortcut(int keyCode, KeyEvent event) { + public boolean onKeyShortcut(int keyCode, @NonNull KeyEvent event) { return false; } @@ -658,7 +655,7 @@ public class Dialog implements DialogInterface, Window.Callback, * The default implementation will cancel the dialog when a touch * happens outside of the window bounds. */ - public boolean onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(@NonNull MotionEvent event) { if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) { cancel(); return true; @@ -681,7 +678,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @return Return true if you have consumed the event, false if you haven't. * The default implementation always returns false. */ - public boolean onTrackballEvent(MotionEvent event) { + public boolean onTrackballEvent(@NonNull MotionEvent event) { return false; } @@ -710,25 +707,30 @@ public class Dialog implements DialogInterface, Window.Callback, * @return Return true if you have consumed the event, false if you haven't. * The default implementation always returns false. */ - public boolean onGenericMotionEvent(MotionEvent event) { + public boolean onGenericMotionEvent(@NonNull MotionEvent event) { return false; } + @Override public void onWindowAttributesChanged(WindowManager.LayoutParams params) { if (mDecor != null) { mWindowManager.updateViewLayout(mDecor, params); } } + @Override public void onContentChanged() { } + @Override public void onWindowFocusChanged(boolean hasFocus) { } + @Override public void onAttachedToWindow() { } + @Override public void onDetachedFromWindow() { } @@ -747,7 +749,8 @@ public class Dialog implements DialogInterface, Window.Callback, * * @return boolean Return true if this event was consumed. */ - public boolean dispatchKeyEvent(KeyEvent event) { + @Override + public boolean dispatchKeyEvent(@NonNull KeyEvent event) { if ((mOnKeyListener != null) && (mOnKeyListener.onKey(this, event.getKeyCode(), event))) { return true; } @@ -767,7 +770,8 @@ public class Dialog implements DialogInterface, Window.Callback, * @param event The key shortcut event. * @return True if this event was consumed. */ - public boolean dispatchKeyShortcutEvent(KeyEvent event) { + @Override + public boolean dispatchKeyShortcutEvent(@NonNull KeyEvent event) { if (mWindow.superDispatchKeyShortcutEvent(event)) { return true; } @@ -784,7 +788,8 @@ public class Dialog implements DialogInterface, Window.Callback, * * @return boolean Return true if this event was consumed. */ - public boolean dispatchTouchEvent(MotionEvent ev) { + @Override + public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { if (mWindow.superDispatchTouchEvent(ev)) { return true; } @@ -801,7 +806,8 @@ public class Dialog implements DialogInterface, Window.Callback, * * @return boolean Return true if this event was consumed. */ - public boolean dispatchTrackballEvent(MotionEvent ev) { + @Override + public boolean dispatchTrackballEvent(@NonNull MotionEvent ev) { if (mWindow.superDispatchTrackballEvent(ev)) { return true; } @@ -818,14 +824,16 @@ public class Dialog implements DialogInterface, Window.Callback, * * @return boolean Return true if this event was consumed. */ - public boolean dispatchGenericMotionEvent(MotionEvent ev) { + @Override + public boolean dispatchGenericMotionEvent(@NonNull MotionEvent ev) { if (mWindow.superDispatchGenericMotionEvent(ev)) { return true; } return onGenericMotionEvent(ev); } - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + @Override + public boolean dispatchPopulateAccessibilityEvent(@NonNull AccessibilityEvent event) { event.setClassName(getClass().getName()); event.setPackageName(mContext.getPackageName()); @@ -840,6 +848,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * @see Activity#onCreatePanelView(int) */ + @Override public View onCreatePanelView(int featureId) { return null; } @@ -847,7 +856,8 @@ public class Dialog implements DialogInterface, Window.Callback, /** * @see Activity#onCreatePanelMenu(int, Menu) */ - public boolean onCreatePanelMenu(int featureId, Menu menu) { + @Override + public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) { if (featureId == Window.FEATURE_OPTIONS_PANEL) { return onCreateOptionsMenu(menu); } @@ -858,10 +868,10 @@ public class Dialog implements DialogInterface, Window.Callback, /** * @see Activity#onPreparePanel(int, View, Menu) */ + @Override public boolean onPreparePanel(int featureId, View view, Menu menu) { if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { - boolean goforit = onPrepareOptionsMenu(menu); - return goforit && menu.hasVisibleItems(); + return onPrepareOptionsMenu(menu) && menu.hasVisibleItems(); } return true; } @@ -869,6 +879,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * @see Activity#onMenuOpened(int, Menu) */ + @Override public boolean onMenuOpened(int featureId, Menu menu) { if (featureId == Window.FEATURE_ACTION_BAR) { mActionBar.dispatchMenuVisibilityChanged(true); @@ -879,6 +890,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * @see Activity#onMenuItemSelected(int, MenuItem) */ + @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { return false; } @@ -886,6 +898,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * @see Activity#onPanelClosed(int, Menu) */ + @Override public void onPanelClosed(int featureId, Menu menu) { if (featureId == Window.FEATURE_ACTION_BAR) { mActionBar.dispatchMenuVisibilityChanged(false); @@ -900,7 +913,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @see Activity#onCreateOptionsMenu(Menu) * @see #getOwnerActivity() */ - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(@NonNull Menu menu) { return true; } @@ -912,21 +925,21 @@ public class Dialog implements DialogInterface, Window.Callback, * @see Activity#onPrepareOptionsMenu(Menu) * @see #getOwnerActivity() */ - public boolean onPrepareOptionsMenu(Menu menu) { + public boolean onPrepareOptionsMenu(@NonNull Menu menu) { return true; } /** * @see Activity#onOptionsItemSelected(MenuItem) */ - public boolean onOptionsItemSelected(MenuItem item) { + public boolean onOptionsItemSelected(@NonNull MenuItem item) { return false; } /** * @see Activity#onOptionsMenuClosed(Menu) */ - public void onOptionsMenuClosed(Menu menu) { + public void onOptionsMenuClosed(@NonNull Menu menu) { } /** @@ -959,47 +972,49 @@ public class Dialog implements DialogInterface, Window.Callback, /** * @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) */ + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { } /** * @see Activity#registerForContextMenu(View) */ - public void registerForContextMenu(View view) { + public void registerForContextMenu(@NonNull View view) { view.setOnCreateContextMenuListener(this); } /** * @see Activity#unregisterForContextMenu(View) */ - public void unregisterForContextMenu(View view) { + public void unregisterForContextMenu(@NonNull View view) { view.setOnCreateContextMenuListener(null); } /** * @see Activity#openContextMenu(View) */ - public void openContextMenu(View view) { + public void openContextMenu(@NonNull View view) { view.showContextMenu(); } /** * @see Activity#onContextItemSelected(MenuItem) */ - public boolean onContextItemSelected(MenuItem item) { + public boolean onContextItemSelected(@NonNull MenuItem item) { return false; } /** * @see Activity#onContextMenuClosed(Menu) */ - public void onContextMenuClosed(Menu menu) { + public void onContextMenuClosed(@NonNull Menu menu) { } /** * This hook is called when the user signals the desire to start a search. */ - public boolean onSearchRequested(SearchEvent searchEvent) { + @Override + public boolean onSearchRequested(@NonNull SearchEvent searchEvent) { mSearchEvent = searchEvent; return onSearchRequested(); } @@ -1007,6 +1022,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * This hook is called when the user signals the desire to start a search. */ + @Override public boolean onSearchRequested() { final SearchManager searchManager = (SearchManager) mContext .getSystemService(Context.SEARCH_SERVICE); @@ -1029,13 +1045,10 @@ public class Dialog implements DialogInterface, Window.Callback, * @return SearchEvent The SearchEvent that triggered the {@link * #onSearchRequested} callback. */ - public final SearchEvent getSearchEvent() { + public final @Nullable SearchEvent getSearchEvent() { return mSearchEvent; } - /** - * {@inheritDoc} - */ @Override public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { if (mActionBar != null && mActionModeTypeStarting == ActionMode.TYPE_PRIMARY) { @@ -1044,9 +1057,6 @@ public class Dialog implements DialogInterface, Window.Callback, return null; } - /** - * {@inheritDoc} - */ @Override public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) { try { @@ -1063,6 +1073,7 @@ public class Dialog implements DialogInterface, Window.Callback, * Note that if you override this method you should always call through * to the superclass implementation by calling super.onActionModeStarted(mode). */ + @Override @CallSuper public void onActionModeStarted(ActionMode mode) { mActionMode = mode; @@ -1074,6 +1085,7 @@ public class Dialog implements DialogInterface, Window.Callback, * Note that if you override this method you should always call through * to the superclass implementation by calling super.onActionModeFinished(mode). */ + @Override @CallSuper public void onActionModeFinished(ActionMode mode) { if (mode == mActionMode) { @@ -1139,7 +1151,7 @@ public class Dialog implements DialogInterface, Window.Callback, * Convenience for calling * {@link android.view.Window#setFeatureDrawableUri}. */ - public final void setFeatureDrawableUri(int featureId, Uri uri) { + public final void setFeatureDrawableUri(int featureId, @Nullable Uri uri) { getWindow().setFeatureDrawableUri(featureId, uri); } @@ -1147,7 +1159,7 @@ public class Dialog implements DialogInterface, Window.Callback, * Convenience for calling * {@link android.view.Window#setFeatureDrawable(int, Drawable)}. */ - public final void setFeatureDrawable(int featureId, Drawable drawable) { + public final void setFeatureDrawable(int featureId, @Nullable Drawable drawable) { getWindow().setFeatureDrawable(featureId, drawable); } @@ -1159,7 +1171,7 @@ public class Dialog implements DialogInterface, Window.Callback, getWindow().setFeatureDrawableAlpha(featureId, alpha); } - public LayoutInflater getLayoutInflater() { + public @NonNull LayoutInflater getLayoutInflater() { return getWindow().getLayoutInflater(); } @@ -1191,6 +1203,7 @@ public class Dialog implements DialogInterface, Window.Callback, * Cancel the dialog. This is essentially the same as calling {@link #dismiss()}, but it will * also call your {@link DialogInterface.OnCancelListener} (if registered). */ + @Override public void cancel() { if (!mCanceled && mCancelMessage != null) { mCanceled = true; @@ -1211,7 +1224,7 @@ public class Dialog implements DialogInterface, Window.Callback, * * @param listener The {@link DialogInterface.OnCancelListener} to use. */ - public void setOnCancelListener(final OnCancelListener listener) { + public void setOnCancelListener(@Nullable OnCancelListener listener) { if (mCancelAndDismissTaken != null) { throw new IllegalStateException( "OnCancelListener is already taken by " @@ -1229,7 +1242,7 @@ public class Dialog implements DialogInterface, Window.Callback, * @param msg The msg to send when the dialog is canceled. * @see #setOnCancelListener(android.content.DialogInterface.OnCancelListener) */ - public void setCancelMessage(final Message msg) { + public void setCancelMessage(@Nullable Message msg) { mCancelMessage = msg; } @@ -1237,7 +1250,7 @@ public class Dialog implements DialogInterface, Window.Callback, * Set a listener to be invoked when the dialog is dismissed. * @param listener The {@link DialogInterface.OnDismissListener} to use. */ - public void setOnDismissListener(final OnDismissListener listener) { + public void setOnDismissListener(@Nullable OnDismissListener listener) { if (mCancelAndDismissTaken != null) { throw new IllegalStateException( "OnDismissListener is already taken by " @@ -1254,7 +1267,7 @@ public class Dialog implements DialogInterface, Window.Callback, * Sets a listener to be invoked when the dialog is shown. * @param listener The {@link DialogInterface.OnShowListener} to use. */ - public void setOnShowListener(OnShowListener listener) { + public void setOnShowListener(@Nullable OnShowListener listener) { if (listener != null) { mShowMessage = mListenersHandler.obtainMessage(SHOW, listener); } else { @@ -1266,13 +1279,13 @@ public class Dialog implements DialogInterface, Window.Callback, * Set a message to be sent when the dialog is dismissed. * @param msg The msg to send when the dialog is dismissed. */ - public void setDismissMessage(final Message msg) { + public void setDismissMessage(@Nullable Message msg) { mDismissMessage = msg; } /** @hide */ - public boolean takeCancelAndDismissListeners(String msg, final OnCancelListener cancel, - final OnDismissListener dismiss) { + public boolean takeCancelAndDismissListeners(@Nullable String msg, + @Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss) { if (mCancelAndDismissTaken != null) { mCancelAndDismissTaken = null; } else if (mCancelMessage != null || mDismissMessage != null) { @@ -1306,15 +1319,15 @@ public class Dialog implements DialogInterface, Window.Callback, /** * Sets the callback that will be called if a key is dispatched to the dialog. */ - public void setOnKeyListener(final OnKeyListener onKeyListener) { + public void setOnKeyListener(@Nullable OnKeyListener onKeyListener) { mOnKeyListener = onKeyListener; } private static final class ListenersHandler extends Handler { - private WeakReference<DialogInterface> mDialog; + private final WeakReference<DialogInterface> mDialog; public ListenersHandler(Dialog dialog) { - mDialog = new WeakReference<DialogInterface>(dialog); + mDialog = new WeakReference<>(dialog); } @Override diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index bb3c7191f4e3..c745644adf13 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1033,11 +1033,11 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * false if it is not. */ public void setUserVisibleHint(boolean isVisibleToUser) { - if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && mFragmentManager != null) { + if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && isAdded()) { mFragmentManager.performPendingDeferredStart(this); } mUserVisibleHint = isVisibleToUser; - mDeferStart = !isVisibleToUser; + mDeferStart = mState < STARTED && !isVisibleToUser; } /** diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 3f2238537d88..4c4f128216f4 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -440,6 +440,11 @@ public class ResourcesManager { compatInfo); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); + if (DEBUG) { + Slog.d(TAG, "createBaseActivityResources activity=" + activityToken + + " with key=" + key); + } + synchronized (this) { final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( activityToken); @@ -651,6 +656,16 @@ public class ResourcesManager { activityResources.overrideConfig.setToDefaults(); } + if (DEBUG) { + Throwable here = new Throwable(); + here.fillInStackTrace(); + Slog.d(TAG, "updating resources override for activity=" + activityToken + + " from oldConfig=" + Configuration.resourceQualifierString(oldConfig) + + " to newConfig=" + + Configuration.resourceQualifierString(activityResources.overrideConfig), + here); + } + final boolean activityHasOverrideConfig = !activityResources.overrideConfig.equals(Configuration.EMPTY); @@ -692,9 +707,15 @@ public class ResourcesManager { oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId, rebasedOverrideConfig, oldKey.mCompatInfo); + if (DEBUG) { + Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey + + " to newKey=" + newKey); + } + ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey); if (resourcesImpl == null) { resourcesImpl = createResourcesImpl(newKey); + mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl)); } if (resourcesImpl != resources.getImpl()) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 656c7ff01e92..45aa6b44a8f1 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -5654,10 +5654,9 @@ public class DevicePolicyManager { } /** - * @hide * Return if this user is a managed profile of another user. An admin can become the profile * owner of a managed profile with {@link #ACTION_PROVISION_MANAGED_PROFILE} and of a managed - * user with {@link #ACTION_PROVISION_MANAGED_USER}. + * user with {@link #createAndManageUser} * @param admin Which profile owner this request is associated with. * @return if this user is a managed profile of another user. */ diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java index 7d698b3ef5a6..01f82e693dee 100644 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -284,6 +284,8 @@ public class BluetoothGattCharacteristic implements Parcelable { out.writeInt(mInstance); out.writeInt(mProperties); out.writeInt(mPermissions); + out.writeInt(mKeySize); + out.writeInt(mWriteType); out.writeTypedList(mDescriptors); } @@ -303,6 +305,8 @@ public class BluetoothGattCharacteristic implements Parcelable { mInstance = in.readInt(); mProperties = in.readInt(); mPermissions = in.readInt(); + mKeySize = in.readInt(); + mWriteType = in.readInt(); mDescriptors = new ArrayList<BluetoothGattDescriptor>(); diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java index e355a1c0a539..35437a1fd707 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -144,7 +144,7 @@ public final class BluetoothManager { } /** - * + * * Get a list of devices that match any of the given connection * states. * diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e89cbd7091ae..39bc783b53ac 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -130,6 +130,7 @@ public abstract class PackageManager { MATCH_DISABLED_COMPONENTS, MATCH_DISABLED_UNTIL_USED_COMPONENTS, MATCH_SYSTEM_ONLY, + MATCH_FACTORY_ONLY, MATCH_DEBUG_TRIAGED_MISSING, }) @Retention(RetentionPolicy.SOURCE) @@ -415,6 +416,13 @@ public abstract class PackageManager { public static final int MATCH_SYSTEM_ONLY = 0x00100000; /** + * Internal {@link PackageInfo} flag: include only components on the system image. + * This will not return information on any unbundled update to system components. + * @hide + */ + public static final int MATCH_FACTORY_ONLY = 0x00200000; + + /** * Internal flag used to indicate that a system component has done their * homework and verified that they correctly handle packages and components * that come and go over time. In particular: diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 984a960b5308..65e0b9204726 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -113,6 +113,13 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public static final int PROTECTION_FLAG_PREINSTALLED = 0x400; /** + * Additional flag for {@link #protectionLevel}, corresponding + * to the <code>setup</code> value of + * {@link android.R.attr#protectionLevel}. + */ + public static final int PROTECTION_FLAG_SETUP = 0x800; + + /** * Mask for {@link #protectionLevel}: the basic protection type. */ public static final int PROTECTION_MASK_BASE = 0xf; @@ -226,6 +233,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { if ((level&PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0) { protLevel += "|preinstalled"; } + if ((level&PermissionInfo.PROTECTION_FLAG_SETUP) != 0) { + protLevel += "|setup"; + } return protLevel; } diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java index 644e29fcaedf..ae44f1d68c3c 100644 --- a/core/java/android/hardware/location/ContextHubInfo.java +++ b/core/java/android/hardware/location/ContextHubInfo.java @@ -37,6 +37,7 @@ public class ContextHubInfo { private float mStoppedPowerDrawMw; private float mSleepPowerDrawMw; private float mPeakPowerDrawMw; + private int mMaxPacketLengthBytes; private int[] mSupportedSensors; @@ -46,6 +47,27 @@ public class ContextHubInfo { } /** + * returns the maximum number of bytes that can be sent per message to the hub + * + * @return int - maximum bytes that can be transmitted in a + * single packet + */ + public int getMaxPacketLengthBytes() { + return mMaxPacketLengthBytes; + } + + /** + * set the context hub unique identifer + * + * @param bytes - Maximum number of bytes per message + * + * @hide + */ + public void setMaxPacketLenBytes(int bytes) { + mMaxPacketLengthBytes = bytes; + } + + /** * get the context hub unique identifer * * @return int - unique system wide identifier @@ -374,4 +396,4 @@ public class ContextHubInfo { return new ContextHubInfo[size]; } }; -} +}
\ No newline at end of file diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 4ddf7673b58c..89edaa9780f6 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -43,23 +43,6 @@ public final class ContextHubManager { private Handler mCallbackHandler; /** - * A special context hub identifier meaning any possible hub on the system. - */ - public static final int ANY_HUB = -1; - /** - * A constant denoting a message to load a a Nano App - */ - public static final int MSG_LOAD_NANO_APP = 1; - /** - * A constant denoting a message to unload a a Nano App - */ - public static final int MSG_UNLOAD_NANO_APP = 2; - /** - * A constant denoting a message to send a message - */ - public static final int MSG_DATA_SEND = 3; - - /** * An interface to receive asynchronous communication from the context hub. */ public abstract static class Callback { @@ -69,7 +52,7 @@ public final class ContextHubManager { * Callback function called on message receipt from context hub. * * @param hubHandle Handle (system-wide unique identifier) of the hub of the message. - * @param nanoAppHandle Handle (unique identifier) for the app that sent the message. + * @param nanoAppHandle Handle (unique identifier) for app instance that sent the message. * @param message The context hub message. * * @see ContextHubMessage @@ -89,7 +72,7 @@ public final class ContextHubManager { try { retVal = getBinder().getContextHubHandles(); } catch (RemoteException e) { - Log.e(TAG, "Could not fetch context hub handles : " + e); + Log.w(TAG, "Could not fetch context hub handles : " + e); } return retVal; } @@ -107,7 +90,7 @@ public final class ContextHubManager { try { retVal = getBinder().getContextHubInfo(hubHandle); } catch (RemoteException e) { - Log.e(TAG, "Could not fetch context hub info :" + e); + Log.w(TAG, "Could not fetch context hub info :" + e); } return retVal; @@ -126,6 +109,7 @@ public final class ContextHubManager { */ public int loadNanoApp(int hubHandle, NanoApp app) { int retVal = -1; + if (app == null) { return retVal; } @@ -133,7 +117,7 @@ public final class ContextHubManager { try { retVal = getBinder().loadNanoApp(hubHandle, app); } catch (RemoteException e) { - Log.e(TAG, "Could not fetch load nanoApp :" + e); + Log.w(TAG, "Could not load nanoApp :" + e); } return retVal; @@ -152,7 +136,7 @@ public final class ContextHubManager { try { retVal = getBinder().unloadNanoApp(nanoAppHandle); } catch (RemoteException e) { - Log.e(TAG, "Could not fetch unload nanoApp :" + e); + Log.w(TAG, "Could not fetch unload nanoApp :" + e); } return retVal; @@ -172,7 +156,7 @@ public final class ContextHubManager { try { retVal = getBinder().getNanoAppInstanceInfo(nanoAppHandle); } catch (RemoteException e) { - Log.e(TAG, "Could not fetch nanoApp info :" + e); + Log.w(TAG, "Could not fetch nanoApp info :" + e); } return retVal; @@ -193,7 +177,7 @@ public final class ContextHubManager { try { retVal = getBinder().findNanoAppOnHub(hubHandle, filter); } catch (RemoteException e) { - Log.e(TAG, "Could not query nanoApp instance :" + e); + Log.w(TAG, "Could not query nanoApp instance :" + e); } return retVal; } @@ -212,10 +196,14 @@ public final class ContextHubManager { public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage message) { int retVal = -1; + if (message == null || message.getData() == null) { + Log.w(TAG, "null ptr"); + return retVal; + } try { retVal = getBinder().sendMessage(hubHandle, nanoAppHandle, message); } catch (RemoteException e) { - Log.e(TAG, "Could not fetch send message :" + e.toString()); + Log.w(TAG, "Could not send message :" + e.toString()); } return retVal; @@ -247,7 +235,7 @@ public final class ContextHubManager { public int registerCallback(Callback callback, Handler handler) { synchronized(this) { if (mCallback != null) { - Log.e(TAG, "Max number of callbacks reached!"); + Log.w(TAG, "Max number of callbacks reached!"); return -1; } mCallback = callback; @@ -268,7 +256,7 @@ public final class ContextHubManager { public int unregisterCallback(Callback callback) { synchronized(this) { if (callback != mCallback) { - Log.e(TAG, "Cannot recognize callback!"); + Log.w(TAG, "Cannot recognize callback!"); return -1; } @@ -311,11 +299,11 @@ public final class ContextHubManager { try { getBinder().registerCallback(mClientCallback); } catch (RemoteException e) { - Log.e(TAG, "Could not register callback:" + e); + Log.w(TAG, "Could not register callback:" + e); } } else { - Log.d(TAG, "failed to getService"); + Log.w(TAG, "failed to getService"); } } diff --git a/core/java/android/hardware/location/ContextHubMessage.java b/core/java/android/hardware/location/ContextHubMessage.java index 954e97dc7fce..bca2ae6d2e8f 100644 --- a/core/java/android/hardware/location/ContextHubMessage.java +++ b/core/java/android/hardware/location/ContextHubMessage.java @@ -16,10 +16,10 @@ package android.hardware.location; - import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; import java.util.Arrays; @@ -32,6 +32,9 @@ public class ContextHubMessage { private int mVersion; private byte[]mData; + private static final String TAG = "ContextHubMessage"; + + /** * Get the message type * @@ -106,9 +109,11 @@ public class ContextHubMessage { private ContextHubMessage(Parcel in) { mType = in.readInt(); mVersion = in.readInt(); - byte[] byteBuffer = new byte[in.readInt()]; - in.readByteArray(byteBuffer); + int bufferLength = in.readInt(); + mData = new byte[bufferLength]; + in.readByteArray(mData); } + public void writeToParcel(Parcel out, int flags) { out.writeInt(mType); out.writeInt(mVersion); diff --git a/core/java/android/hardware/location/ContextHubService.java b/core/java/android/hardware/location/ContextHubService.java index 274babe6a930..b65e24e3b81e 100644 --- a/core/java/android/hardware/location/ContextHubService.java +++ b/core/java/android/hardware/location/ContextHubService.java @@ -29,12 +29,30 @@ import java.util.HashMap; */ public class ContextHubService extends IContextHubService.Stub { + public static final String CONTEXTHUB_SERVICE = "contexthub_service"; + private static final String TAG = "ContextHubService"; private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE; private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '" + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware"; - public static final String CONTEXTHUB_SERVICE = "contexthub_service"; + + public static final int ANY_HUB = -1; + public static final int MSG_LOAD_NANO_APP = 5; + public static final int MSG_UNLOAD_NANO_APP = 2; + + private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown"; + private static final String PRE_LOADED_APP_NAME = PRE_LOADED_GENERIC_UNKNOWN; + private static final String PRE_LOADED_APP_PUBLISHER = PRE_LOADED_GENERIC_UNKNOWN; + private static final int PRE_LOADED_APP_MEM_REQ = 0; + + private static final int MSG_HEADER_SIZE = 4; + private static final int MSG_FIELD_TYPE = 0; + private static final int MSG_FIELD_VERSION = 1; + private static final int MSG_FIELD_HUB_HANDLE = 2; + private static final int MSG_FIELD_APP_INSTANCE = 3; + + private static final int OS_APP_INSTANCE = -1; private final Context mContext; @@ -42,44 +60,27 @@ public class ContextHubService extends IContextHubService.Stub { private ContextHubInfo[] mContextHubInfo; private IContextHubCallback mCallback; + private native int nativeSendMessage(int[] header, byte[] data); + private native ContextHubInfo[] nativeInitialize(); + + public ContextHubService(Context context) { mContext = context; mContextHubInfo = nativeInitialize(); + mNanoAppHash = new HashMap<Integer, NanoAppInstanceInfo>(); for (int i = 0; i < mContextHubInfo.length; i++) { - Log.v(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId() + Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId() + ", name: " + mContextHubInfo[i].getName()); } } - private native int nativeSendMessage(int[] header, byte[] data); - private native ContextHubInfo[] nativeInitialize(); - @Override - public int registerCallback(IContextHubCallback callback) throws RemoteException{ + public int registerCallback(IContextHubCallback callback) throws RemoteException { checkPermissions(); - mCallback = callback; - return 0; - } - - - private int onMessageReceipt(int[] header, byte[] data) { - if (mCallback != null) { - // TODO : Defend against unexpected header sizes - // Add abstraction for magic numbers - // onMessageRecipt should pass the right arguments - ContextHubMessage msg = new ContextHubMessage(header[0], header[1], data); - - try { - mCallback.onMessageReceipt(0, 0, msg); - } catch (Exception e) { - Log.e(TAG, "Exception " + e + " when calling remote callback"); - return -1; - } - } else { - Log.d(TAG, "Message Callback is NULL"); + synchronized(this) { + mCallback = callback; } - return 0; } @@ -118,14 +119,17 @@ public class ContextHubService extends IContextHubService.Stub { } // Call Native interface here - int[] msgHeader = new int[8]; - msgHeader[0] = contextHubHandle; - msgHeader[1] = app.getAppId(); - msgHeader[2] = app.getAppVersion(); - msgHeader[3] = ContextHubManager.MSG_LOAD_NANO_APP; - msgHeader[4] = 0; // Loading hints - - return nativeSendMessage(msgHeader, app.getAppBinary()); + int[] msgHeader = new int[MSG_HEADER_SIZE]; + msgHeader[MSG_FIELD_HUB_HANDLE] = contextHubHandle; + msgHeader[MSG_FIELD_APP_INSTANCE] = OS_APP_INSTANCE; + msgHeader[MSG_FIELD_VERSION] = 0; + msgHeader[MSG_FIELD_TYPE] = MSG_LOAD_NANO_APP; + + if (nativeSendMessage(msgHeader, app.getAppBinary()) != 0) { + return -1; + } + // Do not add an entry to mNanoAppInstance Hash yet. The HAL may reject the app + return 0; } @Override @@ -137,12 +141,18 @@ public class ContextHubService extends IContextHubService.Stub { } // Call Native interface here - int[] msgHeader = new int[8]; - msgHeader[0] = info.getContexthubId(); - msgHeader[1] = ContextHubManager.MSG_UNLOAD_NANO_APP; - msgHeader[2] = info.getHandle(); + int[] msgHeader = new int[MSG_HEADER_SIZE]; + msgHeader[MSG_FIELD_HUB_HANDLE] = ANY_HUB; + msgHeader[MSG_FIELD_APP_INSTANCE] = OS_APP_INSTANCE; + msgHeader[MSG_FIELD_VERSION] = 0; + msgHeader[MSG_FIELD_TYPE] = MSG_UNLOAD_NANO_APP; + + if(nativeSendMessage(msgHeader, null) != 0) { + return -1; + } - return nativeSendMessage(msgHeader, null); + // Do not add an entry to mNanoAppInstance Hash yet. The HAL may reject the app + return 0; } @Override @@ -166,7 +176,7 @@ public class ContextHubService extends IContextHubService.Stub { for(Integer nanoAppInstance : mNanoAppHash.keySet()) { NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance); - if(filter.testMatch(info)){ + if (filter.testMatch(info)){ foundInstances.add(nanoAppInstance); } } @@ -183,12 +193,12 @@ public class ContextHubService extends IContextHubService.Stub { public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException { checkPermissions(); - int[] msgHeader = new int[8]; - msgHeader[0] = ContextHubManager.MSG_DATA_SEND; - msgHeader[1] = hubHandle; - msgHeader[2] = nanoAppHandle; - msgHeader[3] = msg.getMsgType(); - msgHeader[4] = msg.getVersion(); + + int[] msgHeader = new int[MSG_HEADER_SIZE]; + msgHeader[MSG_FIELD_HUB_HANDLE] = hubHandle; + msgHeader[MSG_FIELD_APP_INSTANCE] = nanoAppHandle; + msgHeader[MSG_FIELD_VERSION] = msg.getVersion(); + msgHeader[MSG_FIELD_TYPE] = msg.getMsgType(); return nativeSendMessage(msgHeader, msg.getData()); } @@ -196,5 +206,52 @@ public class ContextHubService extends IContextHubService.Stub { private void checkPermissions() { mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE); } -} + private int onMessageReceipt(int[] header, byte[] data) { + if (header == null || data == null || header.length < MSG_HEADER_SIZE) { + return -1; + } + + synchronized(this) { + if (mCallback != null) { + ContextHubMessage msg = new ContextHubMessage(header[MSG_FIELD_TYPE], + header[MSG_FIELD_VERSION], + data); + + try { + mCallback.onMessageReceipt(header[MSG_FIELD_HUB_HANDLE], + header[MSG_FIELD_APP_INSTANCE], + msg); + } catch (Exception e) { + Log.w(TAG, "Exception " + e + " when calling remote callback"); + return -1; + } + } else { + Log.d(TAG, "Message Callback is NULL"); + } + } + + return 0; + } + + private int addAppInstance(int hubHandle, int appInstanceHandle, long appId, int appVersion) { + // App Id encodes vendor & version + NanoAppInstanceInfo appInfo = new NanoAppInstanceInfo(); + + appInfo.setAppId(appId); + appInfo.setAppVersion(appVersion); + appInfo.setName(PRE_LOADED_APP_NAME); + appInfo.setContexthubId(hubHandle); + appInfo.setHandle(appInstanceHandle); + appInfo.setPublisher(PRE_LOADED_APP_PUBLISHER); + appInfo.setNeededExecMemBytes(PRE_LOADED_APP_MEM_REQ); + appInfo.setNeededReadMemBytes(PRE_LOADED_APP_MEM_REQ); + appInfo.setNeededWriteMemBytes(PRE_LOADED_APP_MEM_REQ); + + mNanoAppHash.put(appInstanceHandle, appInfo); + Log.d(TAG, "Added app instance " + appInstanceHandle + " with id " + appId + + " version " + appVersion); + + return 0; + } +} diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java index 369f9e44e8d9..8db70e9c53f1 100644 --- a/core/java/android/hardware/location/NanoAppFilter.java +++ b/core/java/android/hardware/location/NanoAppFilter.java @@ -20,6 +20,7 @@ package android.hardware.location; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; /** * @hide @@ -27,6 +28,8 @@ import android.os.Parcelable; @SystemApi public class NanoAppFilter { + private static final String TAG = "NanoAppFilter"; + // The appId, can be set to APP_ID_ANY private long mAppId; @@ -54,6 +57,10 @@ public class NanoAppFilter { * If this flag is set, only versions strictly less than the version specified shall match. */ public static final int FLAGS_VERSION_LESS_THAN = 4; + /** + * If this flag is set, only versions strictly equal to the + * version specified shall match. + */ public static final int FLAGS_VERSION_STRICTLY_EQUAL = 8; /** @@ -117,14 +124,9 @@ public class NanoAppFilter { * @return true if this is a match, false otherwise */ public boolean testMatch(NanoAppInstanceInfo info) { - if ((mContextHubId == HUB_ANY || info.getContexthubId() == mContextHubId) && + return (mContextHubId == HUB_ANY || info.getContexthubId() == mContextHubId) && (mAppId == APP_ANY || info.getAppId() == mAppId) && - // (mAppIdVendorMask == VENDOR_ANY) TODO : Expose Vendor mask cleanly - (versionsMatch(mVersionRestrictionMask, mAppVersion, info.getAppVersion()))) { - return true; - } else { - return false; - } + (versionsMatch(mVersionRestrictionMask, mAppVersion, info.getAppVersion())); } public static final Parcelable.Creator<NanoAppFilter> CREATOR diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java index ac62919b7931..977f645db199 100644 --- a/core/java/android/hardware/location/NanoAppInstanceInfo.java +++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java @@ -29,7 +29,7 @@ public class NanoAppInstanceInfo { private String mPublisher; private String mName; - private int mAppId; + private long mAppId; private int mAppVersion; private int mNeededReadMemBytes; @@ -59,6 +59,8 @@ public class NanoAppInstanceInfo { * set the publisher name for the app * * @param publisher - name of the publisher + * + * @hide */ public void setPublisher(String publisher) { mPublisher = publisher; @@ -77,6 +79,8 @@ public class NanoAppInstanceInfo { * set the name of the app * * @param name - name of the app + * + * @hide */ public void setName(String name) { mName = name; @@ -87,7 +91,7 @@ public class NanoAppInstanceInfo { * * @return int - application identifier */ - public int getAppId() { + public long getAppId() { return mAppId; } @@ -95,8 +99,10 @@ public class NanoAppInstanceInfo { * Set the application identifier * * @param appId - application identifier + * + * @hide */ - public void setAppId(int appId) { + public void setAppId(long appId) { mAppId = appId; } @@ -113,6 +119,8 @@ public class NanoAppInstanceInfo { * Set the application version * * @param appVersion - version of the app + * + * @hide */ public void setAppVersion(int appVersion) { mAppVersion = appVersion; @@ -131,6 +139,8 @@ public class NanoAppInstanceInfo { * Set the read memory needed by the app * * @param neededReadMemBytes - readable Memory needed in bytes + * + * @hide */ public void setNeededReadMemBytes(int neededReadMemBytes) { mNeededReadMemBytes = neededReadMemBytes; @@ -150,6 +160,8 @@ public class NanoAppInstanceInfo { * * @param neededWriteMemBytes - writable memory needed by the * app + * + * @hide */ public void setNeededWriteMemBytes(int neededWriteMemBytes) { mNeededWriteMemBytes = neededWriteMemBytes; @@ -169,6 +181,8 @@ public class NanoAppInstanceInfo { * * @param neededExecMemBytes - executable memory needed by the * app + * + * @hide */ public void setNeededExecMemBytes(int neededExecMemBytes) { mNeededExecMemBytes = neededExecMemBytes; @@ -187,6 +201,8 @@ public class NanoAppInstanceInfo { * set the sensors needed by this app * * @param neededSensors - all the sensors needed by this app + * + * @hide */ public void setNeededSensors(int[] neededSensors) { mNeededSensors = neededSensors; @@ -206,6 +222,8 @@ public class NanoAppInstanceInfo { * * @param outputEvents - the events that may be generated by * this app + * + * @hide */ public void setOutputEvents(int[] outputEvents) { mOutputEvents = outputEvents; @@ -224,6 +242,8 @@ public class NanoAppInstanceInfo { * set the context hub identifier * * @param contexthubId - system wide unique identifier + * + * @hide */ public void setContexthubId(int contexthubId) { mContexthubId = contexthubId; @@ -242,6 +262,8 @@ public class NanoAppInstanceInfo { * set the handle for an app instance * * @param handle - handle to this instance + * + * @hide */ public void setHandle(int handle) { mHandle = handle; @@ -252,7 +274,7 @@ public class NanoAppInstanceInfo { mPublisher = in.readString(); mName = in.readString(); - mAppId = in.readInt(); + mAppId = in.readLong(); mAppVersion = in.readInt(); mNeededReadMemBytes = in.readInt(); mNeededWriteMemBytes = in.readInt(); @@ -274,7 +296,7 @@ public class NanoAppInstanceInfo { public void writeToParcel(Parcel out, int flags) { out.writeString(mPublisher); out.writeString(mName); - out.writeInt(mAppId); + out.writeLong(mAppId); out.writeInt(mAppVersion); out.writeInt(mContexthubId); out.writeInt(mNeededReadMemBytes); @@ -286,7 +308,6 @@ public class NanoAppInstanceInfo { out.writeInt(mOutputEvents.length); out.writeIntArray(mOutputEvents); - } public static final Parcelable.Creator<NanoAppInstanceInfo> CREATOR diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index e9c196d0cdad..721c94ef1c7d 100644 --- a/core/java/android/print/PrintAttributes.java +++ b/core/java/android/print/PrintAttributes.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringRes; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources.NotFoundException; @@ -31,6 +32,7 @@ import android.util.ArraySet; import android.util.Log; import com.android.internal.R; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -49,7 +51,7 @@ public final class PrintAttributes implements Parcelable { @IntDef(flag = true, value = { COLOR_MODE_MONOCHROME, COLOR_MODE_COLOR }) - public @interface ColorMode { + @interface ColorMode { } /** Color mode: Monochrome color scheme, for example one color is used. */ public static final int COLOR_MODE_MONOCHROME = 1 << 0; @@ -64,7 +66,7 @@ public final class PrintAttributes implements Parcelable { @IntDef(flag = true, value = { DUPLEX_MODE_NONE, DUPLEX_MODE_LONG_EDGE, DUPLEX_MODE_SHORT_EDGE }) - public @interface DuplexMode { + @interface DuplexMode { } /** Duplex mode: No duplexing. */ public static final int DUPLEX_MODE_NONE = 1 << 0; @@ -76,23 +78,29 @@ public final class PrintAttributes implements Parcelable { private static final int VALID_DUPLEX_MODES = DUPLEX_MODE_NONE | DUPLEX_MODE_LONG_EDGE | DUPLEX_MODE_SHORT_EDGE; - private MediaSize mMediaSize; - private Resolution mResolution; - private Margins mMinMargins; + private @Nullable MediaSize mMediaSize; + private @Nullable Resolution mResolution; + private @Nullable Margins mMinMargins; - private int mColorMode; - private int mDuplexMode; + private @IntRange(from = 0) int mColorMode; + private @IntRange(from = 0) int mDuplexMode; PrintAttributes() { /* hide constructor */ } private PrintAttributes(@NonNull Parcel parcel) { - mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null; - mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null; - mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; + mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null; + mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null; + mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null; mColorMode = parcel.readInt(); + if (mColorMode != 0) { + enforceValidColorMode(mColorMode); + } mDuplexMode = parcel.readInt(); + if (mDuplexMode != 0) { + enforceValidDuplexMode(mDuplexMode); + } } /** @@ -179,7 +187,7 @@ public final class PrintAttributes implements Parcelable { * @see #COLOR_MODE_COLOR * @see #COLOR_MODE_MONOCHROME */ - public @ColorMode int getColorMode() { + public @IntRange(from = 0) int getColorMode() { return mColorMode; } @@ -214,13 +222,13 @@ public final class PrintAttributes implements Parcelable { /** * Gets the duplex mode. * - * @return The duplex mode. + * @return The duplex mode or zero if not set. * * @see #DUPLEX_MODE_NONE * @see #DUPLEX_MODE_LONG_EDGE * @see #DUPLEX_MODE_SHORT_EDGE */ - public @DuplexMode int getDuplexMode() { + public @IntRange(from = 0) int getDuplexMode() { return mDuplexMode; } @@ -448,7 +456,7 @@ public final class PrintAttributes implements Parcelable { private static final String LOG_TAG = "MediaSize"; private static final Map<String, MediaSize> sIdToMediaSizeMap = - new ArrayMap<String, MediaSize>(); + new ArrayMap<>(); /** * Unknown media size in portrait mode. @@ -781,15 +789,15 @@ public final class PrintAttributes implements Parcelable { new MediaSize("JPN_YOU4", "android", R.string.mediasize_japanese_you4, 4134, 9252); - private final String mId; + private final @NonNull String mId; /**@hide */ - public final String mLabel; + public final @NonNull String mLabel; /**@hide */ - public final String mPackageName; + public final @Nullable String mPackageName; /**@hide */ - public final int mLabelResId; - private final int mWidthMils; - private final int mHeightMils; + public final @StringRes int mLabelResId; + private final @IntRange(from = 1) int mWidthMils; + private final @IntRange(from = 1) int mHeightMils; /** * Creates a new instance. @@ -808,29 +816,7 @@ public final class PrintAttributes implements Parcelable { */ public MediaSize(String id, String packageName, int labelResId, int widthMils, int heightMils) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("id cannot be empty."); - } - if (TextUtils.isEmpty(packageName)) { - throw new IllegalArgumentException("packageName cannot be empty."); - } - if (labelResId <= 0) { - throw new IllegalArgumentException("labelResId must be greater than zero."); - } - if (widthMils <= 0) { - throw new IllegalArgumentException("widthMils " - + "cannot be less than or equal to zero."); - } - if (heightMils <= 0) { - throw new IllegalArgumentException("heightMils " - + "cannot be less than or euqual to zero."); - } - mPackageName = packageName; - mId = id; - mLabelResId = labelResId; - mWidthMils = widthMils; - mHeightMils = heightMils; - mLabel = null; + this(id, null, packageName, widthMils, heightMils, labelResId); // Build this mapping only for predefined media sizes. sIdToMediaSizeMap.put(mId, this); @@ -851,26 +837,7 @@ public final class PrintAttributes implements Parcelable { */ public MediaSize(@NonNull String id, @NonNull String label, @IntRange(from = 1) int widthMils, @IntRange(from = 1) int heightMils) { - if (TextUtils.isEmpty(id)) { - throw new IllegalArgumentException("id cannot be empty."); - } - if (TextUtils.isEmpty(label)) { - throw new IllegalArgumentException("label cannot be empty."); - } - if (widthMils <= 0) { - throw new IllegalArgumentException("widthMils " - + "cannot be less than or equal to zero."); - } - if (heightMils <= 0) { - throw new IllegalArgumentException("heightMils " - + "cannot be less than or euqual to zero."); - } - mId = id; - mLabel = label; - mWidthMils = widthMils; - mHeightMils = heightMils; - mLabelResId = 0; - mPackageName = null; + this(id, label, null, widthMils, heightMils, 0); } /** @@ -890,15 +857,37 @@ public final class PrintAttributes implements Parcelable { return definedMediaSizes; } - /** @hide */ - public MediaSize(String id, String label, String packageName, - int widthMils, int heightMils, int labelResId) { + /** + * Creates a new instance. + * + * @param id The unique media size id. It is unique amongst other media sizes + * supported by the printer. + * @param label The <strong>localized</strong> human readable label. + * @param packageName The name of the creating package. + * @param widthMils The width in mils (thousands of an inch). + * @param heightMils The height in mils (thousands of an inch). + * @param labelResId The resource if of a human readable label. + * + * @throws IllegalArgumentException If the id is empty or the label is unset + * or the widthMils is less than or equal to zero or the heightMils is less + * than or equal to zero. + * + * @hide + */ + public MediaSize(String id, String label, String packageName, int widthMils, int heightMils, + int labelResId) { mPackageName = packageName; - mId = id; + mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty."); mLabelResId = labelResId; - mWidthMils = widthMils; - mHeightMils = heightMils; + mWidthMils = Preconditions.checkArgumentPositive(widthMils, "widthMils cannot be " + + "less than or equal to zero."); + mHeightMils = Preconditions.checkArgumentPositive(heightMils, "heightMils cannot be " + + "less than or equal to zero."); mLabel = label; + + // The label has to be either a string ot a StringRes + Preconditions.checkArgument(!TextUtils.isEmpty(label) != + (!TextUtils.isEmpty(packageName) && labelResId != 0), "label cannot be empty."); } /** @@ -926,10 +915,7 @@ public final class PrintAttributes implements Parcelable { try { return packageManager.getResourcesForApplication( mPackageName).getString(mLabelResId); - } catch (NotFoundException nfe) { - Log.w(LOG_TAG, "Could not load resouce" + mLabelResId - + " from package " + mPackageName); - } catch (NameNotFoundException nnfee) { + } catch (NotFoundException | NameNotFoundException e) { Log.w(LOG_TAG, "Could not load resouce" + mLabelResId + " from package " + mPackageName); } @@ -1084,10 +1070,10 @@ public final class PrintAttributes implements Parcelable { * the one with 300 DPI resolution. */ public static final class Resolution { - private final String mId; - private final String mLabel; - private final int mHorizontalDpi; - private final int mVerticalDpi; + private final @NonNull String mId; + private final @NonNull String mLabel; + private final @IntRange(from = 1) int mHorizontalDpi; + private final @IntRange(from = 1) int mVerticalDpi; /** * Creates a new instance. @@ -1244,8 +1230,7 @@ public final class PrintAttributes implements Parcelable { * @param rightMils The right margin in mils (thousands of an inch). * @param bottomMils The bottom margin in mils (thousands of an inch). */ - public Margins(@IntRange(from = 0) int leftMils, @IntRange(from = 0) int topMils, - @IntRange(from = 0) int rightMils, @IntRange(from = 0) int bottomMils) { + public Margins(int leftMils, int topMils, int rightMils, int bottomMils) { mTopMils = topMils; mLeftMils = leftMils; mRightMils = rightMils; @@ -1257,7 +1242,7 @@ public final class PrintAttributes implements Parcelable { * * @return The left margin. */ - public @IntRange(from = 0) int getLeftMils() { + public int getLeftMils() { return mLeftMils; } @@ -1266,7 +1251,7 @@ public final class PrintAttributes implements Parcelable { * * @return The top margin. */ - public @IntRange(from = 0) int getTopMils() { + public int getTopMils() { return mTopMils; } @@ -1275,7 +1260,7 @@ public final class PrintAttributes implements Parcelable { * * @return The right margin. */ - public @IntRange(from = 0) int getRightMils() { + public int getRightMils() { return mRightMils; } @@ -1284,7 +1269,7 @@ public final class PrintAttributes implements Parcelable { * * @return The bottom margin. */ - public @IntRange(from = 0) int getBottomMils() { + public int getBottomMils() { return mBottomMils; } diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java index db3b6f47af43..bec6f290dbdf 100644 --- a/core/java/android/print/PrintDocumentInfo.java +++ b/core/java/android/print/PrintDocumentInfo.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -114,8 +115,8 @@ public final class PrintDocumentInfo implements Parcelable { */ public static final int CONTENT_TYPE_PHOTO = 1; - private String mName; - private int mPageCount; + private @NonNull String mName; + private @IntRange(from = -1) int mPageCount; private int mContentType; private long mDataSize; @@ -144,10 +145,11 @@ public final class PrintDocumentInfo implements Parcelable { * @param parcel Data from which to initialize. */ private PrintDocumentInfo(Parcel parcel) { - mName = parcel.readString(); + mName = Preconditions.checkStringNotEmpty(parcel.readString()); mPageCount = parcel.readInt(); + Preconditions.checkArgument(mPageCount == PAGE_COUNT_UNKNOWN || mPageCount > 0); mContentType = parcel.readInt(); - mDataSize = parcel.readLong(); + mDataSize = Preconditions.checkArgumentNonnegative(parcel.readLong()); } /** @@ -180,7 +182,7 @@ public final class PrintDocumentInfo implements Parcelable { * @see #CONTENT_TYPE_DOCUMENT * @see #CONTENT_TYPE_PHOTO */ - public @ContentType int getContentType() { + public int getContentType() { return mContentType; } @@ -262,13 +264,13 @@ public final class PrintDocumentInfo implements Parcelable { builder.append("PrintDocumentInfo{"); builder.append("name=").append(mName); builder.append(", pageCount=").append(mPageCount); - builder.append(", contentType=").append(contentTyepToString(mContentType)); + builder.append(", contentType=").append(contentTypeToString(mContentType)); builder.append(", dataSize=").append(mDataSize); builder.append("}"); return builder.toString(); } - private String contentTyepToString(int contentType) { + private String contentTypeToString(int contentType) { switch (contentType) { case CONTENT_TYPE_DOCUMENT: { return "CONTENT_TYPE_DOCUMENT"; diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java index d13879ba6914..01c23f6734b0 100644 --- a/core/java/android/print/PrinterCapabilitiesInfo.java +++ b/core/java/android/print/PrinterCapabilitiesInfo.java @@ -24,11 +24,13 @@ import android.print.PrintAttributes.DuplexMode; import android.print.PrintAttributes.Margins; import android.print.PrintAttributes.MediaSize; import android.print.PrintAttributes.Resolution; +import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.IntConsumer; /** * This class represents the capabilities of a printer. Instances @@ -55,9 +57,9 @@ public final class PrinterCapabilitiesInfo implements Parcelable { private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0); - private Margins mMinMargins = DEFAULT_MARGINS; - private List<MediaSize> mMediaSizes; - private List<Resolution> mResolutions; + private @NonNull Margins mMinMargins = DEFAULT_MARGINS; + private @NonNull List<MediaSize> mMediaSizes; + private @NonNull List<Resolution> mResolutions; private int mColorModes; private int mDuplexModes; @@ -205,15 +207,37 @@ public final class PrinterCapabilitiesInfo implements Parcelable { return builder.build(); } + /** + * Call enforceSingle for each bit in the mask. + * + * @param mask The mask + * @param enforceSingle The function to call + */ + private static void enforceValidMask(int mask, IntConsumer enforceSingle) { + int current = mask; + while (current > 0) { + final int currentMode = (1 << Integer.numberOfTrailingZeros(current)); + current &= ~currentMode; + enforceSingle.accept(currentMode); + } + } + private PrinterCapabilitiesInfo(Parcel parcel) { - mMinMargins = readMargins(parcel); + mMinMargins = Preconditions.checkNotNull(readMargins(parcel)); readMediaSizes(parcel); readResolutions(parcel); mColorModes = parcel.readInt(); + enforceValidMask(mColorModes, + (currentMode) -> PrintAttributes.enforceValidColorMode(currentMode)); + mDuplexModes = parcel.readInt(); + enforceValidMask(mDuplexModes, + (currentMode) -> PrintAttributes.enforceValidDuplexMode(currentMode)); readDefaults(parcel); + Preconditions.checkArgument(mMediaSizes.size() > mDefaults[PROPERTY_MEDIA_SIZE]); + Preconditions.checkArgument(mResolutions.size() > mDefaults[PROPERTY_RESOLUTION]); } @Override @@ -537,12 +561,8 @@ public final class PrinterCapabilitiesInfo implements Parcelable { */ public @NonNull Builder setColorModes(@ColorMode int colorModes, @ColorMode int defaultColorMode) { - int currentModes = colorModes; - while (currentModes > 0) { - final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); - currentModes &= ~currentMode; - PrintAttributes.enforceValidColorMode(currentMode); - } + enforceValidMask(colorModes, + (currentMode) -> PrintAttributes.enforceValidColorMode(currentMode)); PrintAttributes.enforceValidColorMode(defaultColorMode); mPrototype.mColorModes = colorModes; mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode; @@ -568,12 +588,8 @@ public final class PrinterCapabilitiesInfo implements Parcelable { */ public @NonNull Builder setDuplexModes(@DuplexMode int duplexModes, @DuplexMode int defaultDuplexMode) { - int currentModes = duplexModes; - while (currentModes > 0) { - final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes)); - currentModes &= ~currentMode; - PrintAttributes.enforceValidDuplexMode(currentMode); - } + enforceValidMask(duplexModes, + (currentMode) -> PrintAttributes.enforceValidDuplexMode(currentMode)); PrintAttributes.enforceValidDuplexMode(defaultDuplexMode); mPrototype.mDuplexModes = duplexModes; mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode; diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 7af0b05f9cc5..0557d138eb74 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -35,7 +35,6 @@ import android.util.Slog; import android.view.ActionMode; import android.view.Display; import android.view.KeyEvent; -import android.view.KeyboardShortcutGroup; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 5bcf1027f384..4ba97d5f8bea 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -180,7 +180,12 @@ interface IWindowManager // caller must call setNewConfiguration() sometime later. Configuration updateOrientationFromAppTokens(in Configuration currentConfig, IBinder freezeThisOneIfNeeded); - void setNewConfiguration(in Configuration config); + // Notify window manager of the new configuration. Returns an array of stack ids that's + // affected by the update, ActivityManager should resize these stacks. + int[] setNewConfiguration(in Configuration config); + + // Retrieves the new bounds after the configuration update evaluated by window manager. + Rect getBoundsForNewConfiguration(int stackId); void startFreezingScreen(int exitAnim, int enterAnim); void stopFreezingScreen(); diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java index c2bd347687a6..eee925df5038 100644 --- a/core/java/android/view/KeyboardShortcutInfo.java +++ b/core/java/android/view/KeyboardShortcutInfo.java @@ -51,7 +51,7 @@ public final class KeyboardShortcutInfo implements Parcelable { mLabel = label; mIcon = icon; mBaseCharacter = MIN_VALUE; - checkArgument(keycode > KeyEvent.KEYCODE_UNKNOWN && keycode <= KeyEvent.getMaxKeyCode()); + checkArgument(keycode >= KeyEvent.KEYCODE_UNKNOWN && keycode <= KeyEvent.getMaxKeyCode()); mKeycode = keycode; mModifiers = modifiers; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3ee6a8d1a179..784164d243f1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -18079,13 +18079,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * to clear the previous drawable. setVisible first while we still have the callback set. */ if (mBackground != null) { - // It's possible for this method to be invoked from the View constructor before - // subclass constructors have run. Drawables can and should trigger invalidations - // and other activity with their callback on visibility changes, which shouldn't - // happen before subclass constructors finish. However, we won't have set the - // drawable as visible until the view becomes attached. This guard below keeps - // multiple calls to this method from constructors from causing issues. - if (mBackground.isVisible()) { + if (isAttachedToWindow()) { mBackground.setVisible(false, false); } mBackground.setCallback(null); @@ -18320,13 +18314,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (mForegroundInfo.mDrawable != null) { - // It's possible for this method to be invoked from the View constructor before - // subclass constructors have run. Drawables can and should trigger invalidations - // and other activity with their callback on visibility changes, which shouldn't - // happen before subclass constructors finish. However, we won't have set the - // drawable as visible until the view becomes attached. This guard below keeps - // multiple calls to this method from constructors from causing issues. - if (mForegroundInfo.mDrawable.isVisible()) { + if (isAttachedToWindow()) { mForegroundInfo.mDrawable.setVisible(false, false); } mForegroundInfo.mDrawable.setCallback(null); diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index d884f199dd86..cc496dc72d7c 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -573,8 +573,12 @@ public final class WebViewFactory { intent.getDataString().substring("package:".length())); } - private static IWebViewUpdateService getUpdateService() { - return IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate")); + private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate"; + + /** @hide */ + public static IWebViewUpdateService getUpdateService() { + return IWebViewUpdateService.Stub.asInterface( + ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME)); } private static native boolean nativeReserveAddressSpace(long addressSpaceToReserve); diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java index d106dba4dd3a..5d091c91abfb 100644 --- a/core/java/android/webkit/WebViewProviderInfo.java +++ b/core/java/android/webkit/WebViewProviderInfo.java @@ -16,12 +16,16 @@ package android.webkit; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import java.util.Arrays; -/** @hide */ +/** + * @hide + */ +@SystemApi public final class WebViewProviderInfo implements Parcelable { public WebViewProviderInfo(String packageName, String description, diff --git a/core/java/android/webkit/WebViewUpdateService.java b/core/java/android/webkit/WebViewUpdateService.java new file mode 100644 index 000000000000..4e83d8834062 --- /dev/null +++ b/core/java/android/webkit/WebViewUpdateService.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.webkit; + +import android.annotation.SystemApi; +import android.os.RemoteException; + +/** + * @hide + */ +@SystemApi +public final class WebViewUpdateService { + + private WebViewUpdateService () {} + + /** + * Fetch all packages that could potentially implement WebView. + */ + public static WebViewProviderInfo[] getAllWebViewPackages() { + try { + return getUpdateService().getAllWebViewPackages(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Fetch all packages that could potentially implement WebView and are currently valid. + */ + public static WebViewProviderInfo[] getValidWebViewPackages() { + try { + return getUpdateService().getValidWebViewPackages(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Used by DevelopmentSetting to get the name of the WebView provider currently in use. + */ + public static String getCurrentWebViewPackageName() { + try { + return getUpdateService().getCurrentWebViewPackageName(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + private static IWebViewUpdateService getUpdateService() { + return WebViewFactory.getUpdateService(); + } +} diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java index 027f6d6f2727..9d228cf667b1 100644 --- a/core/java/android/widget/ArrayAdapter.java +++ b/core/java/android/widget/ArrayAdapter.java @@ -20,6 +20,7 @@ import android.annotation.ArrayRes; import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.util.Log; @@ -61,17 +62,13 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp private final LayoutInflater mInflater; - /** - * Contains the list of objects that represent the data of this ArrayAdapter. - * The content of this list is referred to as "the array" in the documentation. - */ - private List<T> mObjects; + private final Context mContext; /** * The resource indicating what views to inflate to display the content of this * array adapter. */ - private int mResource; + private final int mResource; /** * The resource indicating what views to inflate to display the content of this @@ -80,7 +77,13 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp private int mDropDownResource; /** - * If the inflated resource is not a TextView, {@link #mFieldId} is used to find + * Contains the list of objects that represent the data of this ArrayAdapter. + * The content of this list is referred to as "the array" in the documentation. + */ + private List<T> mObjects; + + /** + * If the inflated resource is not a TextView, {@code mFieldId} is used to find * a TextView inside the inflated views hierarchy. This field must contain the * identifier that matches the one defined in the resource file. */ @@ -92,8 +95,6 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp */ private boolean mNotifyOnChange = true; - private Context mContext; - // A copy of the original mObjects array, initialized from and then used instead as soon as // the mFilter ArrayFilter is used. mObjects will then only contain the filtered values. private ArrayList<T> mOriginalValues; @@ -109,8 +110,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * @param resource The resource ID for a layout file containing a TextView to use when * instantiating views. */ - public ArrayAdapter(Context context, @LayoutRes int resource) { - this(context, resource, 0, new ArrayList<T>()); + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource) { + this(context, resource, 0, new ArrayList<>()); } /** @@ -121,8 +122,9 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * instantiating views. * @param textViewResourceId The id of the TextView within the layout resource to be populated */ - public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId) { - this(context, resource, textViewResourceId, new ArrayList<T>()); + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @IdRes int textViewResourceId) { + this(context, resource, textViewResourceId, new ArrayList<>()); } /** @@ -133,7 +135,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * instantiating views. * @param objects The objects to represent in the ListView. */ - public ArrayAdapter(Context context, @LayoutRes int resource, @NonNull T[] objects) { + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) { this(context, resource, 0, Arrays.asList(objects)); } @@ -146,8 +148,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * @param textViewResourceId The id of the TextView within the layout resource to be populated * @param objects The objects to represent in the ListView. */ - public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId, - @NonNull T[] objects) { + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @IdRes int textViewResourceId, @NonNull T[] objects) { this(context, resource, textViewResourceId, Arrays.asList(objects)); } @@ -159,7 +161,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * instantiating views. * @param objects The objects to represent in the ListView. */ - public ArrayAdapter(Context context, @LayoutRes int resource, @NonNull List<T> objects) { + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @NonNull List<T> objects) { this(context, resource, 0, objects); } @@ -172,8 +175,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * @param textViewResourceId The id of the TextView within the layout resource to be populated * @param objects The objects to represent in the ListView. */ - public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId, - @NonNull List<T> objects) { + public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, + @IdRes int textViewResourceId, @NonNull List<T> objects) { mContext = context; mInflater = LayoutInflater.from(context); mResource = mDropDownResource = resource; @@ -186,7 +189,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * * @param object The object to add at the end of the array. */ - public void add(T object) { + public void add(@Nullable T object) { synchronized (mLock) { if (mOriginalValues != null) { mOriginalValues.add(object); @@ -201,8 +204,17 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * Adds the specified Collection at the end of the array. * * @param collection The Collection to add at the end of the array. - */ - public void addAll(Collection<? extends T> collection) { + * @throws UnsupportedOperationException if the <tt>addAll</tt> operation + * is not supported by this list + * @throws ClassCastException if the class of an element of the specified + * collection prevents it from being added to this list + * @throws NullPointerException if the specified collection contains one + * or more null elements and this list does not permit null + * elements, or if the specified collection is null + * @throws IllegalArgumentException if some property of an element of the + * specified collection prevents it from being added to this list + */ + public void addAll(@NonNull Collection<? extends T> collection) { synchronized (mLock) { if (mOriginalValues != null) { mOriginalValues.addAll(collection); @@ -235,7 +247,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * @param object The object to insert into the array. * @param index The index at which the object must be inserted. */ - public void insert(T object, int index) { + public void insert(@Nullable T object, int index) { synchronized (mLock) { if (mOriginalValues != null) { mOriginalValues.add(index, object); @@ -251,7 +263,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * * @param object The object to remove. */ - public void remove(T object) { + public void remove(@Nullable T object) { synchronized (mLock) { if (mOriginalValues != null) { mOriginalValues.remove(object); @@ -282,7 +294,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * @param comparator The comparator used to sort the objects contained * in this adapter. */ - public void sort(Comparator<? super T> comparator) { + public void sort(@NonNull Comparator<? super T> comparator) { synchronized (mLock) { if (mOriginalValues != null) { Collections.sort(mOriginalValues, comparator); @@ -293,9 +305,6 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp if (mNotifyOnChange) notifyDataSetChanged(); } - /** - * {@inheritDoc} - */ @Override public void notifyDataSetChanged() { super.notifyDataSetChanged(); @@ -326,21 +335,17 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * * @return The Context associated with this adapter. */ - public Context getContext() { + public @NonNull Context getContext() { return mContext; } - /** - * {@inheritDoc} - */ + @Override public int getCount() { return mObjects.size(); } - /** - * {@inheritDoc} - */ - public T getItem(int position) { + @Override + public @Nullable T getItem(int position) { return mObjects.get(position); } @@ -351,28 +356,25 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * * @return The position of the specified item. */ - public int getPosition(T item) { + public int getPosition(@Nullable T item) { return mObjects.indexOf(item); } - /** - * {@inheritDoc} - */ + @Override public long getItemId(int position) { return position; } - /** - * {@inheritDoc} - */ - public View getView(int position, View convertView, ViewGroup parent) { + @Override + public @NonNull View getView(int position, @Nullable View convertView, + @NonNull ViewGroup parent) { return createViewFromResource(mInflater, position, convertView, parent, mResource); } - private View createViewFromResource(LayoutInflater inflater, int position, View convertView, - ViewGroup parent, int resource) { - View view; - TextView text; + private @NonNull View createViewFromResource(@NonNull LayoutInflater inflater, int position, + @Nullable View convertView, @NonNull ViewGroup parent, int resource) { + final View view; + final TextView text; if (convertView == null) { view = inflater.inflate(resource, parent, false); @@ -387,6 +389,12 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp } else { // Otherwise, find the TextView field within the layout text = (TextView) view.findViewById(mFieldId); + + if (text == null) { + throw new RuntimeException("Failed to find view with ID " + + mContext.getResources().getResourceName(mFieldId) + + " in item layout"); + } } } catch (ClassCastException e) { Log.e("ArrayAdapter", "You must supply a resource ID for a TextView"); @@ -394,9 +402,9 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp "ArrayAdapter requires the resource ID to be a TextView", e); } - T item = getItem(position); + final T item = getItem(position); if (item instanceof CharSequence) { - text.setText((CharSequence)item); + text.setText((CharSequence) item); } else { text.setText(item.toString()); } @@ -426,7 +434,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * @see #getDropDownView(int, View, ViewGroup) */ @Override - public void setDropDownViewTheme(Resources.Theme theme) { + public void setDropDownViewTheme(@Nullable Resources.Theme theme) { if (theme == null) { mDropDownInflater = null; } else if (theme == mInflater.getContext().getTheme()) { @@ -438,12 +446,13 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp } @Override - public Resources.Theme getDropDownViewTheme() { + public @Nullable Resources.Theme getDropDownViewTheme() { return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme(); } @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { + public View getDropDownView(int position, @Nullable View convertView, + @NonNull ViewGroup parent) { final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater; return createViewFromResource(inflater, position, convertView, parent, mDropDownResource); } @@ -458,16 +467,14 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp * * @return An ArrayAdapter<CharSequence>. */ - public static ArrayAdapter<CharSequence> createFromResource(Context context, + public static @NonNull ArrayAdapter<CharSequence> createFromResource(@NonNull Context context, @ArrayRes int textArrayResId, @LayoutRes int textViewResId) { - CharSequence[] strings = context.getResources().getTextArray(textArrayResId); - return new ArrayAdapter<CharSequence>(context, textViewResId, strings); + final CharSequence[] strings = context.getResources().getTextArray(textArrayResId); + return new ArrayAdapter<>(context, textViewResId, strings); } - /** - * {@inheritDoc} - */ - public Filter getFilter() { + @Override + public @NonNull Filter getFilter() { if (mFilter == null) { mFilter = new ArrayFilter(); } @@ -482,31 +489,31 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp private class ArrayFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { - FilterResults results = new FilterResults(); + final FilterResults results = new FilterResults(); if (mOriginalValues == null) { synchronized (mLock) { - mOriginalValues = new ArrayList<T>(mObjects); + mOriginalValues = new ArrayList<>(mObjects); } } if (prefix == null || prefix.length() == 0) { - ArrayList<T> list; + final ArrayList<T> list; synchronized (mLock) { - list = new ArrayList<T>(mOriginalValues); + list = new ArrayList<>(mOriginalValues); } results.values = list; results.count = list.size(); } else { - String prefixString = prefix.toString().toLowerCase(); + final String prefixString = prefix.toString().toLowerCase(); - ArrayList<T> values; + final ArrayList<T> values; synchronized (mLock) { - values = new ArrayList<T>(mOriginalValues); + values = new ArrayList<>(mOriginalValues); } final int count = values.size(); - final ArrayList<T> newValues = new ArrayList<T>(); + final ArrayList<T> newValues = new ArrayList<>(); for (int i = 0; i < count; i++) { final T value = values.get(i); @@ -517,11 +524,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSp newValues.add(value); } else { final String[] words = valueText.split(" "); - final int wordCount = words.length; - - // Start at index 0, in case valueText starts with space(s) - for (int k = 0; k < wordCount; k++) { - if (words[k].startsWith(prefixString)) { + for (String word : words) { + if (word.startsWith(prefixString)) { newValues.add(value); break; } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 6e3dbd8c5e1d..e3357a7738f2 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -75,8 +75,6 @@ import java.util.TimeZone; */ @Widget public class DatePicker extends FrameLayout { - private static final String LOG_TAG = DatePicker.class.getSimpleName(); - private static final int MODE_SPINNER = 1; private static final int MODE_CALENDAR = 2; diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java index 5adac0195701..332e89cc9653 100755 --- a/core/java/android/widget/DatePickerCalendarDelegate.java +++ b/core/java/android/widget/DatePickerCalendarDelegate.java @@ -378,9 +378,9 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate { mCurrentDate.set(Calendar.MONTH, monthOfYear); mCurrentDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - mDateChangedListener = callBack; - onDateChanged(false, false); + + mDateChangedListener = callBack; } @Override diff --git a/core/java/android/widget/DatePickerSpinnerDelegate.java b/core/java/android/widget/DatePickerSpinnerDelegate.java index 255de79dbf32..d8a3c5638b72 100644 --- a/core/java/android/widget/DatePickerSpinnerDelegate.java +++ b/core/java/android/widget/DatePickerSpinnerDelegate.java @@ -244,6 +244,7 @@ class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate { setDate(year, monthOfYear, dayOfMonth); updateSpinners(); updateCalendarView(); + mOnDateChangedListener = onDateChangedListener; } diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index fe8916bf0761..9ac49172ec89 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -16,17 +16,15 @@ package android.widget; -import java.util.ArrayList; +import com.android.internal.R; +import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StyleRes; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.PorterDuff; import android.graphics.Rect; -import android.graphics.Region; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; @@ -36,8 +34,7 @@ import android.view.ViewGroup; import android.view.ViewHierarchyEncoder; import android.widget.RemoteViews.RemoteView; -import com.android.internal.R; - +import java.util.ArrayList; /** * FrameLayout is designed to block out an area on the screen to display @@ -75,31 +72,29 @@ public class FrameLayout extends ViewGroup { @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingBottom = 0; - private final Rect mSelfBounds = new Rect(); - private final Rect mOverlayBounds = new Rect(); - - private final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1); - - public FrameLayout(Context context) { + private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1); + + public FrameLayout(@NonNull Context context) { super(context); } - - public FrameLayout(Context context, @Nullable AttributeSet attrs) { + + public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - public FrameLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - public FrameLayout( - Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + public FrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); final TypedArray a = context.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.FrameLayout, defStyleAttr, defStyleRes); - - if (a.getBoolean(com.android.internal.R.styleable.FrameLayout_measureAllChildren, false)) { + attrs, R.styleable.FrameLayout, defStyleAttr, defStyleRes); + + if (a.getBoolean(R.styleable.FrameLayout_measureAllChildren, false)) { setMeasureAllChildren(true); } @@ -171,10 +166,6 @@ public class FrameLayout extends ViewGroup { mPaddingBottom + mForegroundPaddingBottom; } - - /** - * {@inheritDoc} - */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); @@ -264,17 +255,13 @@ public class FrameLayout extends ViewGroup { } } } - - /** - * {@inheritDoc} - */ + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); } - void layoutChildren(int left, int top, int right, int bottom, - boolean forceLeftGravity) { + void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); @@ -378,12 +365,9 @@ public class FrameLayout extends ViewGroup { return mMeasureAllChildren; } - /** - * {@inheritDoc} - */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new FrameLayout.LayoutParams(getContext(), attrs); + return new FrameLayout.LayoutParams(getContext(), attrs); } @Override @@ -391,9 +375,6 @@ public class FrameLayout extends ViewGroup { return false; } - /** - * {@inheritDoc} - */ @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; @@ -431,34 +412,30 @@ public class FrameLayout extends ViewGroup { * Per-child layout information for layouts that support margins. * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes} * for a list of all child view attributes that this class supports. - * + * * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity */ public static class LayoutParams extends MarginLayoutParams { /** * The gravity to apply with the View to which these layout parameters * are associated. + * <p> + * The default value is {@code Gravity.TOP | Gravity.START} * * @see android.view.Gravity - * * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity */ - public int gravity = -1; + public int gravity = DEFAULT_CHILD_GRAVITY; - /** - * {@inheritDoc} - */ - public LayoutParams(Context c, AttributeSet attrs) { + public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) { super(c, attrs); - TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout); - gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1); + final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FrameLayout_Layout); + gravity = a.getInt(R.styleable.FrameLayout_Layout_layout_gravity, + DEFAULT_CHILD_GRAVITY); a.recycle(); } - /** - * {@inheritDoc} - */ public LayoutParams(int width, int height) { super(width, height); } @@ -468,9 +445,9 @@ public class FrameLayout extends ViewGroup { * and weight. * * @param width the width, either {@link #MATCH_PARENT}, - * {@link #WRAP_CONTENT} or a fixed size in pixels + * {@link #WRAP_CONTENT} or a fixed size in pixels * @param height the height, either {@link #MATCH_PARENT}, - * {@link #WRAP_CONTENT} or a fixed size in pixels + * {@link #WRAP_CONTENT} or a fixed size in pixels * @param gravity the gravity * * @see android.view.Gravity @@ -480,17 +457,11 @@ public class FrameLayout extends ViewGroup { this.gravity = gravity; } - /** - * {@inheritDoc} - */ - public LayoutParams(ViewGroup.LayoutParams source) { + public LayoutParams(@NonNull ViewGroup.LayoutParams source) { super(source); } - /** - * {@inheritDoc} - */ - public LayoutParams(ViewGroup.MarginLayoutParams source) { + public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) { super(source); } @@ -500,7 +471,7 @@ public class FrameLayout extends ViewGroup { * * @param source The layout params to copy from. */ - public LayoutParams(LayoutParams source) { + public LayoutParams(@NonNull LayoutParams source) { super(source); this.gravity = source.gravity; diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 02065779ef91..222a040d2b3c 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -911,17 +911,11 @@ public class ImageView extends View { } if (mDrawable != null) { - // It's possible for this method to be invoked from the constructor before - // subclass constructors have run. Drawables can and should trigger invalidations - // and other activity with their callback on visibility changes, which shouldn't - // happen before subclass constructors finish. However, we won't have set the - // drawable as visible until the view becomes attached. This guard below keeps - // multiple calls to this method from constructors from causing issues. - if (mDrawable.isVisible()) { - mDrawable.setVisible(false, false); - } mDrawable.setCallback(null); unscheduleDrawable(mDrawable); + if (isAttachedToWindow()) { + mDrawable.setVisible(false, false); + } } mDrawable = d; diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 8bd63dfc2075..6d2cea6b5c3c 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -17,6 +17,7 @@ package android.widget; import android.annotation.ColorInt; +import android.app.ActivityManager.StackId; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; @@ -228,6 +229,11 @@ public class RemoteViews implements Parcelable, Filter { public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { + return onClickHandler(view, pendingIntent, fillInIntent, StackId.INVALID_STACK_ID); + } + + public boolean onClickHandler(View view, PendingIntent pendingIntent, + Intent fillInIntent, int launchStackId) { try { // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? Context context = view.getContext(); @@ -239,6 +245,10 @@ public class RemoteViews implements Parcelable, Filter { 0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); } + + if (launchStackId != StackId.INVALID_STACK_ID) { + opts.setLaunchStackId(launchStackId); + } context.startIntentSender( pendingIntent.getIntentSender(), fillInIntent, Intent.FLAG_ACTIVITY_NEW_TASK, diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java index 36db5d742b4a..a9d51132cdcc 100644 --- a/core/java/com/android/internal/app/LocaleHelper.java +++ b/core/java/com/android/internal/app/LocaleHelper.java @@ -219,7 +219,7 @@ public class LocaleHelper { public int compare(LocaleStore.LocaleInfo lhs, LocaleStore.LocaleInfo rhs) { // We don't care about the various suggestion types, just "suggested" (!= 0) // and "all others" (== 0) - if (mCountryMode || (lhs.isSuggested() == rhs.isSuggested())) { + if (lhs.isSuggested() == rhs.isSuggested()) { // They are in the same "bucket" (suggested / others), so we compare the text return mCollator.compare( removePrefixForCompare(lhs.getLocale(), lhs.getLabel(mCountryMode)), diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java index c4e6675c2c92..7803e52c45e1 100644 --- a/core/java/com/android/internal/app/LocaleStore.java +++ b/core/java/com/android/internal/app/LocaleStore.java @@ -31,8 +31,9 @@ public class LocaleStore { private static boolean sFullyInitialized = false; public static class LocaleInfo { - private static final int SUGGESTION_TYPE_NONE = 0x00; - private static final int SUGGESTION_TYPE_SIM = 0x01; + private static final int SUGGESTION_TYPE_NONE = 0; + private static final int SUGGESTION_TYPE_SIM = 1 << 0; + private static final int SUGGESTION_TYPE_CFG = 1 << 1; private final Locale mLocale; private final Locale mParent; @@ -273,6 +274,22 @@ public class LocaleStore { final HashSet<String> localizedLocales = new HashSet<>(); for (String localeId : LocalePicker.getSystemAssetLocales()) { LocaleInfo li = new LocaleInfo(localeId); + final String country = li.getLocale().getCountry(); + // All this is to figure out if we should suggest a country + if (!country.isEmpty()) { + LocaleInfo cachedLocale = null; + if (sLocaleCache.containsKey(li.getId())) { // the simple case, e.g. fr-CH + cachedLocale = sLocaleCache.get(li.getId()); + } else { // e.g. zh-TW localized, zh-Hant-TW in cache + final String langScriptCtry = li.getLangScriptKey() + "-" + country; + if (sLocaleCache.containsKey(langScriptCtry)) { + cachedLocale = sLocaleCache.get(langScriptCtry); + } + } + if (cachedLocale != null) { + cachedLocale.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CFG; + } + } localizedLocales.add(li.getLangScriptKey()); } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 0e02ed6f126d..ff680e2002ac 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -243,10 +243,6 @@ public class ResolverActivity extends Activity { return; } - // Prevent the Resolver window from becoming the top fullscreen window and thus from taking - // control of the system bars. - getWindow().clearFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR); - final ResolverDrawerLayout rdl = (ResolverDrawerLayout) findViewById(R.id.contentPanel); if (rdl != null) { rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() { diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index 98102ea8627c..e2d29e313be4 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -49,6 +49,7 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { private static final int TYPE_HEADER_SUGGESTED = 0; private static final int TYPE_HEADER_ALL_OTHERS = 1; private static final int TYPE_LOCALE = 2; + private static final int MIN_REGIONS_FOR_SUGGESTIONS = 6; private ArrayList<LocaleStore.LocaleInfo> mLocaleOptions; private ArrayList<LocaleStore.LocaleInfo> mOriginalLocaleOptions; @@ -171,7 +172,15 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { } private boolean showHeaders() { - if (mCountryMode) { // never show suggestions in country mode + // We don't want to show suggestions for locales with very few regions + // (e.g. Romanian, with 2 regions) + // So we put a (somewhat) arbitrary limit. + // + // The initial idea was to make that limit dependent on the screen height. + // But that would mean rotating the screen could make the suggestions disappear, + // as the number of countries that fits on the screen would be different in portrait + // and landscape mode. + if (mCountryMode && mLocaleOptions.size() < MIN_REGIONS_FOR_SUGGESTIONS) { return false; } return mSuggestionCount != 0 && mSuggestionCount != mLocaleOptions.size(); diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index ee73b90097af..bed5a2e219ae 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -238,6 +238,14 @@ public class ArrayUtils { return total; } + public static int[] convertToIntArray(List<Integer> list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + /** * Adds value to given array if not already present, providing set-like * behavior. diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index 7c8524671716..1ead5b3de1de 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -191,6 +191,21 @@ public class Preconditions { * Ensures that that the argument numeric value is non-negative. * * @param value a numeric long value + * @return the validated numeric value + * @throws IllegalArgumentException if {@code value} was negative + */ + public static long checkArgumentNonnegative(final long value) { + if (value < 0) { + throw new IllegalArgumentException(); + } + + return value; + } + + /** + * Ensures that that the argument numeric value is non-negative. + * + * @param value a numeric long value * @param errorMessage the exception message to use if the check fails * @return the validated numeric value * @throws IllegalArgumentException if {@code value} was negative diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 9d1447836b93..3d892afbb5f3 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1099,7 +1099,8 @@ public class LockPatternUtils { || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC - || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; + || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX + || mode == DevicePolicyManager.PASSWORD_QUALITY_MANAGED; return passwordEnabled && savedPasswordExists(userId); } diff --git a/core/java/com/android/server/backup/ShortcutBackupHelper.java b/core/java/com/android/server/backup/ShortcutBackupHelper.java new file mode 100644 index 000000000000..0b3f2aeac35a --- /dev/null +++ b/core/java/com/android/server/backup/ShortcutBackupHelper.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.backup; + +import android.app.backup.BlobBackupHelper; +import android.content.Context; +import android.content.pm.IShortcutService; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.Slog; + +public class ShortcutBackupHelper extends BlobBackupHelper { + private static final String TAG = "ShortcutBackupAgent"; + private static final int BLOB_VERSION = 1; + + private static final String KEY_USER_FILE = "shortcutuser.xml"; + + public ShortcutBackupHelper() { + super(BLOB_VERSION, KEY_USER_FILE); + } + + private IShortcutService getShortcutService() { + return IShortcutService.Stub.asInterface( + ServiceManager.getService(Context.SHORTCUT_SERVICE)); + } + + @Override + protected byte[] getBackupPayload(String key) { + switch (key) { + case KEY_USER_FILE: + try { + return getShortcutService().getBackupPayload(UserHandle.USER_SYSTEM); + } catch (Exception e) { + Slog.wtf(TAG, "Backup failed", e); + } + break; + default: + Slog.w(TAG, "Unknown key: " + key); + } + return null; + } + + @Override + protected void applyRestoredPayload(String key, byte[] payload) { + switch (key) { + case KEY_USER_FILE: + try { + getShortcutService().applyRestore(payload, UserHandle.USER_SYSTEM); + } catch (Exception e) { + Slog.wtf(TAG, "Restore failed", e); + } + break; + default: + Slog.w(TAG, "Unknown key: " + key); + } + } +} diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java index 181ed5144310..2d12fcda87c1 100644 --- a/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/core/java/com/android/server/backup/SystemBackupAgent.java @@ -17,9 +17,9 @@ package com.android.server.backup; import android.app.IWallpaperManager; +import android.app.backup.BackupAgentHelper; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; -import android.app.backup.BackupAgentHelper; import android.app.backup.FullBackup; import android.app.backup.FullBackupDataOutput; import android.app.backup.WallpaperBackupHelper; @@ -48,6 +48,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String NOTIFICATION_HELPER = "notifications"; private static final String PERMISSION_HELPER = "permissions"; private static final String USAGE_STATS_HELPER = "usage_stats"; + private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager"; // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME // are also used in the full-backup file format, so must not change unless steps are @@ -100,6 +101,7 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this)); addHelper(PERMISSION_HELPER, new PermissionBackupHelper()); addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this)); + addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper()); super.onBackup(oldState, data, newState); } @@ -138,6 +140,7 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this)); addHelper(PERMISSION_HELPER, new PermissionBackupHelper()); addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this)); + addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper()); try { super.onRestore(data, appVersionCode, newState); diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 27b98306b72b..22a81d401df8 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -251,13 +251,6 @@ SkPixelRef* Bitmap::refPixelRefLocked() { void Bitmap::reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) { - { - android::AutoMutex _lock(mLock); - if (mPinnedRefCount) { - ALOGW("Called reconfigure on a bitmap that is in use! This may" - " cause graphical corruption!"); - } - } mPixelRef->reconfigure(info, rowBytes, ctable); } diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 27d8fed51e8a..1844a9870ada 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -823,7 +823,7 @@ namespace PaintGlue { static jfloat doRunAdvance(const Paint* paint, Typeface* typeface, const jchar buf[], jint start, jint count, jint bufSize, jboolean isRtl, jint offset) { int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; - if (offset == count) { + if (offset == start + count) { return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize, nullptr); } diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp index 87247292a73b..42dc983792d2 100644 --- a/core/jni/android_hardware_location_ContextHubService.cpp +++ b/core/jni/android_hardware_location_ContextHubService.cpp @@ -16,23 +16,39 @@ #include "context_hub.h" +#define LOG_NDEBUG 0 +#define LOG_TAG "ContextHubService" + +#include <inttypes.h> +#include <jni.h> +#include <map> +#include <queue> #include <string.h> #include <stdint.h> #include <stdio.h> +#include <stdlib.h> + +#include <cutils/log.h> -#include <jni.h> #include "JNIHelp.h" #include "core_jni_helpers.h" -#include "stdint.h" -#include "stdlib.h" + +//static constexpr int OS_APP_ID=-1; + +static constexpr int MIN_APP_ID=1; +static constexpr int MAX_APP_ID=128; + +static constexpr size_t MSG_HEADER_SIZE=4; +static constexpr int HEADER_FIELD_MSG_TYPE=0; +//static constexpr int HEADER_FIELD_MSG_VERSION=1; +static constexpr int HEADER_FIELD_HUB_HANDLE=2; +static constexpr int HEADER_FIELD_APP_INSTANCE=3; namespace android { namespace { -// TODO: We should share this array_length function widely around Android -// code. /* * Finds the length of a statically-sized array using template trickery that * also prevents it from being applied to the wrong type. @@ -64,35 +80,207 @@ struct jniInfo_s { jmethodID contextHubInfoSetPeakPowerDrawMw; jmethodID contextHubInfoSetSupportedSensors; jmethodID contextHubInfoSetMemoryRegions; + jmethodID contextHubInfoSetMaxPacketLenBytes; jmethodID contextHubServiceMsgReceiptCallback; + jmethodID contextHubServiceAddAppInstance; }; struct context_hub_info_s { - int cookie; + uint32_t *cookies; int numHubs; const struct context_hub_t *hubs; struct context_hub_module_t *contextHubModule; }; +struct app_instance_info_s { + uint32_t hubHandle; // Id of the hub this app is on + int instanceId; // systemwide unique instance id - assigned + struct hub_app_info appInfo; // returned from the HAL + uint64_t truncName; // Possibly truncated name - logging +}; + struct contextHubServiceDb_s { int initialized; context_hub_info_s hubInfo; jniInfo_s jniInfo; + std::queue<int> freeIds; + std::map<int, app_instance_info_s *> appInstances; }; } // unnamed namespace static contextHubServiceDb_s db; -int context_hub_callback(uint32_t hub_id, const struct hub_message_t *msg, +int context_hub_callback(uint32_t hubId, const struct hub_message_t *msg, void *cookie); +const context_hub_t *get_hub_info(int hubHandle) { + if (hubHandle >= 0 && hubHandle < db.hubInfo.numHubs) { + return &db.hubInfo.hubs[hubHandle]; + } + return nullptr; +} + +static int send_msg_to_hub(const hub_message_t *msg, int hubHandle) { + const context_hub_t *info = get_hub_info(hubHandle); + + if (info) { + return db.hubInfo.contextHubModule->send_message(info->hub_id, msg); + } else { + ALOGD("%s: Hub information is null for hubHandle %d", __FUNCTION__, hubHandle); + return -1; + } +} + +static int set_os_app_as_destination(hub_message_t *msg, int hubHandle) { + const context_hub_t *info = get_hub_info(hubHandle); + + if (info) { + msg->app = info->os_app_name; + return 0; + } else { + ALOGD("%s: Hub information is null for hubHandle %d", __FUNCTION__, hubHandle); + return -1; + } +} + +static int get_hub_id_for_app_instance(int id) { + if (db.appInstances.find(id) == db.appInstances.end()) { + ALOGD("%s: Cannot find app for app instance %d", __FUNCTION__, id); + return -1; + } + + int hubHandle = db.appInstances[id]->hubHandle; + + return db.hubInfo.hubs[hubHandle].hub_id; +} + +static int set_dest_app(hub_message_t *msg, int id) { + if (db.appInstances.find(id) == db.appInstances.end()) { + ALOGD("%s: Cannod find app for app instance %d", __FUNCTION__, id); + return -1; + } + + msg->app = db.appInstances[id]->appInfo.name; + return 0; +} + +static void send_query_for_apps() { + hub_message_t msg; + + msg.message_type = CONTEXT_HUB_QUERY_APPS; + msg.message_len = 0; + + for (int i = 0; i < db.hubInfo.numHubs; i++ ) { + ALOGD("Sending query for apps to hub %d", i); + set_os_app_as_destination(&msg, i); + if (send_msg_to_hub(&msg, i) != 0) { + ALOGW("Could not query hub %i for apps", i); + } + } +} + +static int return_id(int id) { + // Note : This method is not thread safe. + // id returned is guarenteed to be in use + db.freeIds.push(id); + return 0; +} + +static int generate_id(void) { + // Note : This method is not thread safe. + int retVal = -1; + + if (!db.freeIds.empty()) { + retVal = db.freeIds.front(); + db.freeIds.pop(); + } + + return retVal; +} + +int add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle, JNIEnv *env) { + // Not checking if the apps are indeed distinct + + app_instance_info_s *entry; + void *appName; + hub_app_name_t *name; + + assert(appInfo && appInfo->name && appInfo->name->app_name); + + entry = (app_instance_info_s *) malloc(sizeof(app_instance_info_s)); + appName = malloc(appInfo->name->app_name_len); + name = (hub_app_name_t *) malloc(sizeof(hub_app_name_t)); + + int appInstanceHandle = generate_id(); + + if (appInstanceHandle < 0 || !appName || !entry || !name) { + ALOGE("Cannot find resources to add app instance %d, %p, %p", + appInstanceHandle, appName, entry); + + free(appName); + free(entry); + free(name); + + if (appInstanceHandle >= 0) { + return_id(appInstanceHandle); + } + + return -1; + } + + memcpy(&(entry->appInfo), appInfo, sizeof(entry->appInfo)); + memcpy(appName, appInfo->name->app_name, appInfo->name->app_name_len); + name->app_name = appName; + name->app_name_len = appInfo->name->app_name_len; + entry->appInfo.name = name; + entry->truncName = 0; + memcpy(&(entry->truncName), name->app_name, + sizeof(entry->truncName) < name->app_name_len ? + sizeof(entry->truncName) : name->app_name_len); + + // Not checking for sanity of hubId + entry->hubHandle = hubHandle; + entry->instanceId = appInstanceHandle; + db.appInstances[appInstanceHandle] = entry; + + // Finally - let the service know of this app instance + env->CallIntMethod(db.jniInfo.jContextHubService, + db.jniInfo.contextHubServiceAddAppInstance, + hubHandle, entry->instanceId, entry->truncName, + entry->appInfo.version); + + ALOGW("Added App 0x%" PRIx64 " on hub Handle %" PRId32 + " as appInstance %d, original name_length %" PRId32, entry->truncName, + entry->hubHandle, appInstanceHandle, name->app_name_len); + + return appInstanceHandle; +} + +int delete_app_instance(int id) { + if (db.appInstances.find(id) == db.appInstances.end()) { + return -1; + } + + return_id(id); + + if (db.appInstances[id]) { + // Losing the const cast below. This is intentional. + free((void *)db.appInstances[id]->appInfo.name->app_name); + free((void *)db.appInstances[id]->appInfo.name); + free(db.appInstances[id]); + db.appInstances.erase(id); + } + + return 0; +} + + static void initContextHubService() { int err = 0; - db.hubInfo.hubs = NULL; + db.hubInfo.hubs = nullptr; db.hubInfo.numHubs = 0; - db.hubInfo.cookie = 0; int i; err = hw_get_module(CONTEXT_HUB_MODULE_ID, @@ -103,26 +291,45 @@ static void initContextHubService() { strerror(-err)); } + // Prep for storing app info + for(i = MIN_APP_ID; i <= MAX_APP_ID; i++) { + db.freeIds.push(i); + } + if (db.hubInfo.contextHubModule) { - ALOGD("Fetching hub info"); - db.hubInfo.numHubs = db.hubInfo.contextHubModule->get_hubs(db.hubInfo.contextHubModule, + int retNumHubs = db.hubInfo.contextHubModule->get_hubs(db.hubInfo.contextHubModule, &db.hubInfo.hubs); - - if (db.hubInfo.numHubs > 0) { - for (i = 0; i < db.hubInfo.numHubs; i++) { - // TODO : Event though one cookie is OK for now, lets change - // this to be one per hub - db.hubInfo.contextHubModule->subscribe_messages(db.hubInfo.hubs[i].hub_id, - context_hub_callback, - &db.hubInfo.cookie); + ALOGD("ContextHubModule returned %d hubs ", retNumHubs); + db.hubInfo.numHubs = retNumHubs; + + if (db.hubInfo.numHubs > 0) { + db.hubInfo.numHubs = retNumHubs; + db.hubInfo.cookies = (uint32_t *)malloc(sizeof(uint32_t) * db.hubInfo.numHubs); + + if (!db.hubInfo.cookies) { + ALOGW("Ran out of memory allocating cookies, bailing"); + return; + } + + for (i = 0; i < db.hubInfo.numHubs; i++) { + db.hubInfo.cookies[i] = db.hubInfo.hubs[i].hub_id; + if (db.hubInfo.contextHubModule->subscribe_messages(db.hubInfo.hubs[i].hub_id, + context_hub_callback, + &db.hubInfo.cookies[i]) == 0) { + } + } } - } + + send_query_for_apps(); + } else { + ALOGW("No Context Hub Module present"); } } static int onMessageReceipt(int *header, int headerLen, char *msg, int msgLen) { JNIEnv *env; - if ((db.jniInfo.vm)->AttachCurrentThread(&env, NULL) != JNI_OK) { + + if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) { return -1; } @@ -132,28 +339,131 @@ static int onMessageReceipt(int *header, int headerLen, char *msg, int msgLen) { env->SetByteArrayRegion(jmsg, 0, msgLen, (jbyte *)msg); env->SetIntArrayRegion(jheader, 0, headerLen, (jint *)header); - - return env->CallIntMethod(db.jniInfo.jContextHubService, + return (env->CallIntMethod(db.jniInfo.jContextHubService, db.jniInfo.contextHubServiceMsgReceiptCallback, - jheader, jmsg); + jheader, jmsg) != 0); +} + +int handle_query_apps_response(char *msg, int msgLen, uint32_t hubHandle) { + int i; + JNIEnv *env; + if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) { + return -1; + } + + int numApps = msgLen/sizeof(hub_app_info); + hub_app_info *info = (hub_app_info *)malloc(msgLen); // handle possible alignment + + if (!info) { + return -1; + } + + memcpy(info, msg, msgLen); + for (i = 0; i < numApps; i++) { + add_app_instance(info, hubHandle, env); + info++; + } + + free(info); + + return 0; +} + + +int handle_os_message(uint32_t msgType, uint32_t hubHandle, + char *msg, int msgLen) { + int retVal; + + switch(msgType) { + case CONTEXT_HUB_APPS_ENABLE: + retVal = 0; + break; + + case CONTEXT_HUB_APPS_DISABLE: + retVal = 0; + break; + + case CONTEXT_HUB_LOAD_APP: + retVal = 0; + break; + + case CONTEXT_HUB_UNLOAD_APP: + retVal = 0; + break; + + case CONTEXT_HUB_QUERY_APPS: + retVal = handle_query_apps_response(msg, msgLen, hubHandle); + break; + + case CONTEXT_HUB_QUERY_MEMORY: + retVal = 0; + break; + + case CONTEXT_HUB_LOAD_OS: + retVal = 0; + break; + + default: + retVal = -1; + break; + + } + + return retVal; +} + +static bool sanity_check_cookie(void *cookie, uint32_t hub_id) { + int *ptr = (int *)cookie; + + if (!ptr || *ptr >= db.hubInfo.numHubs) { + return false; + } + + if (db.hubInfo.hubs[*ptr].hub_id != hub_id) { + return false; + } else { + return true; + } } -int context_hub_callback(uint32_t hub_id, const struct hub_message_t *msg, +int context_hub_callback(uint32_t hubId, + const struct hub_message_t *msg, void *cookie) { - int msgHeader[4]; + int msgHeader[MSG_HEADER_SIZE]; - msgHeader[0] = msg->message_type; - msgHeader[1] = 0; // TODO : HAL does not have a version field - msgHeader[2] = hub_id; + if (!msg) { + return -1; + } + + msgHeader[HEADER_FIELD_MSG_TYPE] = msg->message_type; + + if (!sanity_check_cookie(cookie, hubId)) { + ALOGW("Incorrect cookie %" PRId32 " for cookie %p! Bailing", + hubId, cookie); + + return -1; + } + + msgHeader[HEADER_FIELD_HUB_HANDLE] = *(uint32_t*)cookie; + + if (msgHeader[HEADER_FIELD_MSG_TYPE] < CONTEXT_HUB_TYPE_PRIVATE_MSG_BASE && + msgHeader[HEADER_FIELD_MSG_TYPE] != 0 ) { + handle_os_message(msgHeader[HEADER_FIELD_MSG_TYPE], + msgHeader[HEADER_FIELD_HUB_HANDLE], + (char *)msg->message, + msg->message_len); + } else { + onMessageReceipt(msgHeader, sizeof(msgHeader), + (char *)msg->message, msg->message_len); + } - onMessageReceipt(msgHeader, sizeof(msgHeader), (char *)msg->message, msg->message_len); // TODO : Populate this - return 0; + return 0; } static int init_jni(JNIEnv *env, jobject instance) { if (env->GetJavaVM(&db.jniInfo.vm) != JNI_OK) { - return -1; + return -1; } db.jniInfo.jContextHubService = env->NewGlobalRef(instance); @@ -167,7 +477,6 @@ static int init_jni(JNIEnv *env, jobject instance) { db.jniInfo.memoryRegionsClass = env->FindClass("android/hardware/location/MemoryRegion"); - //TODO :: Add error checking db.jniInfo.contextHubInfoCtor = env->GetMethodID(db.jniInfo.contextHubInfoClass, "<init>", "()V"); db.jniInfo.contextHubInfoSetId = @@ -209,6 +518,9 @@ static int init_jni(JNIEnv *env, jobject instance) { db.jniInfo.contextHubInfoSetMemoryRegions = env->GetMethodID(db.jniInfo.contextHubInfoClass, "setMemoryRegions", "([Landroid/hardware/location/MemoryRegion;)V"); + db.jniInfo.contextHubInfoSetMaxPacketLenBytes = + env->GetMethodID(db.jniInfo.contextHubInfoClass, + "setMaxPacketLenBytes", "(I)V"); db.jniInfo.contextHubServiceMsgReceiptCallback = @@ -218,6 +530,11 @@ static int init_jni(JNIEnv *env, jobject instance) { env->GetMethodID(db.jniInfo.contextHubInfoClass, "setName", "(Ljava/lang/String;)V"); + db.jniInfo.contextHubServiceAddAppInstance = + env->GetMethodID(db.jniInfo.contextHubServiceClass, + "addAppInstance", "(IIJI)I"); + + return 0; } @@ -245,20 +562,29 @@ static jobject constructJContextHubInfo(JNIEnv *env, const struct context_hub_t env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPlatformVersion, hub->platform_version); env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchainVersion, hub->toolchain_version); env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakMips, hub->peak_mips); - env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetStoppedPowerDrawMw, hub->stopped_power_draw_mw); - env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSleepPowerDrawMw, hub->sleep_power_draw_mw); - env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakPowerDrawMw, hub->peak_power_draw_mw); + env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetStoppedPowerDrawMw, + hub->stopped_power_draw_mw); + env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSleepPowerDrawMw, + hub->sleep_power_draw_mw); + env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakPowerDrawMw, + hub->peak_power_draw_mw); + env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMaxPacketLenBytes, + hub->max_supported_msg_len); + // TODO : jintBuf = env->NewIntArray(hub->num_connected_sensors); - // TODO : env->SetIntArrayRegion(jintBuf, 0, hub->num_connected_sensors, hub->connected_sensors); + // TODO : env->SetIntArrayRegion(jintBuf, 0, hub->num_connected_sensors, + // hub->connected_sensors); jintBuf = env->NewIntArray(array_length(dummyConnectedSensors)); env->SetIntArrayRegion(jintBuf, 0, hub->num_connected_sensors, dummyConnectedSensors); + env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSupportedSensors, jintBuf); // We are not getting the memory regions from the CH Hal - change this when it is available - jmemBuf = env->NewObjectArray(0, db.jniInfo.memoryRegionsClass, NULL); + jmemBuf = env->NewObjectArray(0, db.jniInfo.memoryRegionsClass, nullptr); // Note the zero size above. We do not need to set any elements env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMemoryRegions, jmemBuf); + return jHub; } @@ -267,18 +593,18 @@ static jobjectArray nativeInitialize(JNIEnv *env, jobject instance) jobject hub; jobjectArray retArray; - initContextHubService(); - if (init_jni(env, instance) < 0) { - return NULL; + return nullptr; } - // Note : The service is clamping the number of hubs to 1 - db.hubInfo.numHubs = 1; - initContextHubService(); - retArray = env->NewObjectArray(db.hubInfo.numHubs, db.jniInfo.contextHubInfoClass, NULL); + if (db.hubInfo.numHubs > 1) { + ALOGW("Clamping the number of hubs to 1"); + db.hubInfo.numHubs = 1; + } + + retArray = env->NewObjectArray(db.hubInfo.numHubs, db.jniInfo.contextHubInfoClass, nullptr); for(int i = 0; i < db.hubInfo.numHubs; i++) { hub = constructJContextHubInfo(env, &db.hubInfo.hubs[i]); @@ -291,28 +617,27 @@ static jobjectArray nativeInitialize(JNIEnv *env, jobject instance) static jint nativeSendMessage(JNIEnv *env, jobject instance, jintArray header_, jbyteArray data_) { hub_message_t msg; - hub_app_name_t dest; - uint8_t os_name[8]; - - memset(os_name, 0, sizeof(os_name)); + jint retVal = -1; // Default to failure jint *header = env->GetIntArrayElements(header_, 0); - //int numHeaderElements = env->GetArrayLength(header_); + unsigned int numHeaderElements = env->GetArrayLength(header_); jbyte *data = env->GetByteArrayElements(data_, 0); int dataBufferLength = env->GetArrayLength(data_); - /* Assume an int - thats all we understand */ - dest.app_name_len = array_length(os_name); // TODO : Check this - //dest.app_name = &header[1]; - dest.app_name = os_name; - - msg.app = &dest; - - msg.message_type = header[3]; - msg.message_len = dataBufferLength; - msg.message = data; - - jint retVal = db.hubInfo.contextHubModule->send_message(header[0], &msg); + if (numHeaderElements >= MSG_HEADER_SIZE) { + if (set_dest_app(&msg, header[HEADER_FIELD_APP_INSTANCE]) == 0) { + msg.message_type = header[HEADER_FIELD_MSG_TYPE]; + msg.message_len = dataBufferLength; + msg.message = data; + retVal = db.hubInfo.contextHubModule->send_message( + get_hub_id_for_app_instance(header[HEADER_FIELD_APP_INSTANCE]), + &msg); + } else { + ALOGD("Could not find app instance %d", header[HEADER_FIELD_APP_INSTANCE]); + } + } else { + ALOGD("Malformed header len"); + } env->ReleaseIntArrayElements(header_, header, 0); env->ReleaseByteArrayElements(data_, data, 0); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0a4ef989951e..30a59934e0a4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -845,14 +845,14 @@ --> <permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE" android:permissionGroup="android.permission-group.PHONE" - android:protectionLevel="dangerous"/> + android:protectionLevel="signatureOrSystem"/> <!-- @hide Allows an application to Access UCE-OPTIONS. <p>Protection level: dangerous --> <permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE" android:permissionGroup="android.permission-group.PHONE" - android:protectionLevel="dangerous"/> + android:protectionLevel="signatureOrSystem"/> @@ -2004,6 +2004,11 @@ <permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows but does not guarantee access to user passwords at the conclusion of add + account --> + <permission android:name="android.permission.GET_PASSWORD_PRIVILEGED" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows applications to RW to diagnostic resources. <p>Not for use by third-party applications. --> <permission android:name="android.permission.DIAGNOSTIC" @@ -2984,6 +2989,11 @@ <permission android:name="android.permission.BIND_VR_LISTENER_SERVICE" android:protectionLevel="signature" /> + <!-- Allows an application to whitelist tasks during lock task mode + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" + android:protectionLevel="signature|setup" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index d1c08958f934..5b4364d45c73 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -225,6 +225,9 @@ granted any application pre-installed on the system image (not just privileged apps). --> <flag name="preinstalled" value="0x400" /> + <!-- Additional flag from base permission type: this permission can be automatically + granted to the setup wizard app --> + <flag name="setup" value="0x800" /> </attr> <!-- Flags indicating more context for a permission group. --> diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java index 7519627bb0d0..2a24881f67fa 100644 --- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java +++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowActionModeTest.java @@ -22,7 +22,6 @@ import android.test.suitebuilder.annotation.SmallTest; import android.view.ActionMode; import android.view.ActionMode.Callback; import android.view.KeyEvent; -import android.view.KeyboardShortcutGroup; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 0a8c6929d54d..00eff9139dc8 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2157,7 +2157,7 @@ public class AudioManager { final RecordConfigChangeCallbackData cbData = (RecordConfigChangeCallbackData) msg.obj; if (cbData.mCb != null) { - cbData.mCb.onRecordConfigChanged(cbData.mConfigs); + cbData.mCb.onRecordingConfigChanged(cbData.mConfigs); } break; default: @@ -2746,7 +2746,7 @@ public class AudioManager { * @param configs array containing the results of * {@link AudioManager#getActiveRecordingConfigurations()}. */ - public void onRecordConfigChanged(AudioRecordingConfiguration[] configs) {} + public void onRecordingConfigChanged(AudioRecordingConfiguration[] configs) {} } private static class AudioRecordingCallbackInfo { diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index b6ff41ef15d0..621129d63f9c 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -596,14 +596,14 @@ public class AudioTrack implements AudioRouting * AudioTrack player = new AudioTrack.Builder() * .setAudioAttributes(new AudioAttributes.Builder() * .setUsage(AudioAttributes.USAGE_ALARM) - * .setContentType(CONTENT_TYPE_MUSIC) + * .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) * .build()) * .setAudioFormat(new AudioFormat.Builder() * .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - * .setSampleRate(441000) + * .setSampleRate(44100) * .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) * .build()) - * .setBufferSize(minBuffSize) + * .setBufferSizeInBytes(minBuffSize) * .build(); * </pre> * <p> diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index e987885676c9..3d7e74426b7c 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -235,8 +235,6 @@ public class ExifInterface { public static final String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity"; /** Type is int. */ public static final String TAG_SUBSEC_TIME = "SubSecTime"; - /** Type is int. @hide */ - public static final String TAG_SUBSECTIME = "SubSecTime"; /** Type is int. */ public static final String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized"; /** Type is int. */ @@ -447,7 +445,7 @@ public class ExifInterface { new ExifTag(TAG_F_NUMBER, 33437), new ExifTag(TAG_EXPOSURE_PROGRAM, 34850), new ExifTag(TAG_SPECTRAL_SENSITIVITY, 34852), - new ExifTag(TAG_ISO, 34855), + new ExifTag(TAG_ISO_SPEED_RATINGS, 34855), new ExifTag(TAG_OECF, 34856), new ExifTag(TAG_EXIF_VERSION, 36864), new ExifTag(TAG_DATETIME_ORIGINAL, 36867), @@ -1161,7 +1159,7 @@ public class ExifInterface { if (datetime == null) return -1; long msecs = datetime.getTime(); - String subSecs = getAttribute(TAG_SUBSECTIME); + String subSecs = getAttribute(TAG_SUBSEC_TIME); if (subSecs != null) { try { long sub = Long.valueOf(subSecs); @@ -1553,7 +1551,7 @@ public class ExifInterface { convertToDouble(TAG_EXPOSURE_TIME); convertToDouble(TAG_F_NUMBER); convertToDouble(TAG_SUBJECT_DISTANCE); - convertToInt(TAG_ISO); + convertToInt(TAG_ISO_SPEED_RATINGS); convertToDouble(TAG_EXPOSURE_BIAS_VALUE); convertToInt(TAG_WHITE_BALANCE); convertToInt(TAG_LIGHT_SOURCE); diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index b1c1b79732c5..2bd978180c4c 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -2666,12 +2666,13 @@ public final class MediaCodecInfo { public static final int HEVCHighTierLevel62 = 0x2000000; // from OMX_VIDEO_DOLBYVISIONPROFILETYPE - public static final int DolbyVisionProfileDvavDer = 0x1; - public static final int DolbyVisionProfileDvavDen = 0x2; - public static final int DolbyVisionProfileDvheDer = 0x3; - public static final int DolbyVisionProfileDvheDen = 0x4; - public static final int DolbyVisionProfileDvheDtr = 0x5; - public static final int DolbyVisionProfileDvheStn = 0x6; + public static final int DolbyVisionProfileDvavPer = 0x1; + public static final int DolbyVisionProfileDvavPen = 0x2; + public static final int DolbyVisionProfileDvheDer = 0x4; + public static final int DolbyVisionProfileDvheDen = 0x8; + public static final int DolbyVisionProfileDvheDtr = 0x10; + public static final int DolbyVisionProfileDvheStn = 0x20; + public static final int DolbyVisionProfileDvheDth = 0x40; // from OMX_VIDEO_DOLBYVISIONLEVELTYPE public static final int DolbyVisionLevelHd24 = 0x1; diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index acf94f4cf7f6..03dc6995c798 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -116,10 +116,10 @@ public final class TvInputInfo implements Parcelable { private final int mType; private final boolean mIsHardwareInput; - // TODO: Remove mLabel and mIconUri when createTvInputInfo() is removed. - private String mLabel; + // TODO: Remove mIconUri when createTvInputInfo() is removed. private Uri mIconUri; + private final CharSequence mLabel; private final int mLabelResId; private final Icon mIcon; private final Icon mIconStandby; @@ -161,8 +161,8 @@ public final class TvInputInfo implements Parcelable { TvInputInfo info = new TvInputInfo.Builder(context, service) .setHdmiDeviceInfo(hdmiDeviceInfo) .setParentId(parentId) + .setLabel(label) .build(); - info.mLabel = label; info.mIconUri = iconUri; return info; } @@ -215,8 +215,8 @@ public final class TvInputInfo implements Parcelable { throws XmlPullParserException, IOException { TvInputInfo info = new TvInputInfo.Builder(context, service) .setTvInputHardwareInfo(hardwareInfo) + .setLabel(label) .build(); - info.mLabel = label; info.mIconUri = iconUri; return info; } @@ -247,7 +247,7 @@ public final class TvInputInfo implements Parcelable { } private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, - int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, + CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, String setupActivity, String settingsActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) { @@ -255,6 +255,7 @@ public final class TvInputInfo implements Parcelable { mId = id; mType = type; mIsHardwareInput = isHardwareInput; + mLabel = label; mLabelResId = labelResId; mIcon = icon; mIconStandby = iconStandby; @@ -570,7 +571,7 @@ public final class TvInputInfo implements Parcelable { dest.writeString(mId); dest.writeInt(mType); dest.writeByte(mIsHardwareInput ? (byte) 1 : 0); - dest.writeString(mLabel); + TextUtils.writeToParcel(mLabel, dest, flags); dest.writeParcelable(mIconUri, flags); dest.writeInt(mLabelResId); dest.writeParcelable(mIcon, flags); @@ -612,7 +613,7 @@ public final class TvInputInfo implements Parcelable { mId = in.readString(); mType = in.readInt(); mIsHardwareInput = in.readByte() == 1; - mLabel = in.readString(); + mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mIconUri = in.readParcelable(null); mLabelResId = in.readInt(); mIcon = in.readParcelable(null); @@ -660,6 +661,7 @@ public final class TvInputInfo implements Parcelable { private final Context mContext; private final ResolveInfo mResolveInfo; + private CharSequence mLabel; private int mLabelResId; private Icon mIcon; private Icon mIconStandby; @@ -746,12 +748,31 @@ public final class TvInputInfo implements Parcelable { /** * Sets the label. * + * @param label The text to be used as label. + * @return This Builder object to allow for chaining of calls to builder methods. + * @hide + */ + @SystemApi + public Builder setLabel(CharSequence label) { + if (mLabelResId != 0) { + throw new IllegalStateException("Resource ID for label is already set."); + } + this.mLabel = label; + return this; + } + + /** + * Sets the label. + * * @param resId The resource ID of the text to use. * @return This Builder object to allow for chaining of calls to builder methods. * @hide */ @SystemApi public Builder setLabel(int resId) { + if (mLabel != null) { + throw new IllegalStateException("Label text is already set."); + } this.mLabelResId = resId; return this; } @@ -868,8 +889,8 @@ public final class TvInputInfo implements Parcelable { type = TYPE_TUNER; } parseServiceMetadata(type); - return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabelResId, mIcon, - mIconStandby, mIconDisconnected, mSetupActivity, mSettingsActivity, + return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId, + mIcon, mIconStandby, mIconDisconnected, mSetupActivity, mSettingsActivity, mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount, mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras); } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java index dde9bdadebcd..312d9aaa8750 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java @@ -61,7 +61,7 @@ public class ExifInterfaceTest extends AndroidTestCase { private static final String[] EXIF_TAGS = { ExifInterface.TAG_MAKE, ExifInterface.TAG_MODEL, - ExifInterface.TAG_APERTURE, + ExifInterface.TAG_F_NUMBER, ExifInterface.TAG_DATETIME, ExifInterface.TAG_EXPOSURE_TIME, ExifInterface.TAG_FLASH, @@ -77,7 +77,7 @@ public class ExifInterfaceTest extends AndroidTestCase { ExifInterface.TAG_GPS_TIMESTAMP, ExifInterface.TAG_IMAGE_LENGTH, ExifInterface.TAG_IMAGE_WIDTH, - ExifInterface.TAG_ISO, + ExifInterface.TAG_ISO_SPEED_RATINGS, ExifInterface.TAG_ORIENTATION, ExifInterface.TAG_WHITE_BALANCE }; @@ -288,7 +288,7 @@ public class ExifInterfaceTest extends AndroidTestCase { // Checks values. assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make); assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model); - assertFloatTag(exifInterface, ExifInterface.TAG_APERTURE, expectedValue.aperture); + assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture); assertStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime); assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime); assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash); @@ -308,7 +308,7 @@ public class ExifInterfaceTest extends AndroidTestCase { assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp); assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength); assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth); - assertStringTag(exifInterface, ExifInterface.TAG_ISO, expectedValue.iso); + assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso); assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation); assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index 70b478ad6437..87136ef35bc9 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -272,6 +272,7 @@ public abstract class BaseActivity extends Activity @Override public boolean onOptionsItemSelected(MenuItem item) { + Metrics.logMenuAction(this, item.getItemId()); switch (item.getItemId()) { case android.R.id.home: @@ -635,10 +636,12 @@ public abstract class BaseActivity extends Activity return true; } } else if (keyCode == KeyEvent.KEYCODE_TAB) { + Metrics.logKeyboardAction(this, keyCode); // Tab toggles focus on the navigation drawer. toggleNavDrawerFocus(); return true; } else if (keyCode == KeyEvent.KEYCODE_DEL) { + Metrics.logKeyboardAction(this, keyCode); popDir(); return true; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java index 68c0c2a27f8b..d4439d8b480f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java @@ -212,19 +212,22 @@ public class FilesActivity extends BaseActivity { case R.id.menu_create_dir: assert(canCreateDirectory()); showCreateDirectoryDialog(); - return true; + break; case R.id.menu_new_window: createNewWindow(); - return true; + break; case R.id.menu_paste_from_clipboard: DirectoryFragment dir = getDirectoryFragment(); if (dir != null) { dir.pasteFromClipboard(); } - return true; + break; + default: + return super.onOptionsItemSelected(item); } - return super.onOptionsItemSelected(item); + Metrics.logMenuAction(this, item.getItemId()); + return true; } private void createNewWindow() { @@ -346,18 +349,21 @@ public class FilesActivity extends BaseActivity { case KeyEvent.KEYCODE_A: dir = getDirectoryFragment(); if (dir != null) { + Metrics.logKeyboardAction(this, keyCode); dir.selectAllFiles(); } return true; case KeyEvent.KEYCODE_C: dir = getDirectoryFragment(); if (dir != null) { + Metrics.logKeyboardAction(this, keyCode); dir.copySelectedToClipboard(); } return true; case KeyEvent.KEYCODE_V: dir = getDirectoryFragment(); if (dir != null) { + Metrics.logKeyboardAction(this, keyCode); dir.pasteFromClipboard(); } return true; diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java index f1f47c832d66..a4a67f96a3ee 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java +++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java @@ -29,6 +29,7 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.provider.DocumentsContract; import android.util.Log; +import android.view.KeyEvent; import com.android.documentsui.State.ActionType; import com.android.documentsui.model.DocumentInfo; @@ -67,6 +68,10 @@ public final class Metrics { private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled"; private static final String COUNT_STARTUP_MS = "docsui_startup_ms"; private static final String COUNT_DRAWER_OPENED = "docsui_drawer_opened"; + private static final String COUNT_DRAG_N_DROP = "docsui_drag_n_drop"; + private static final String COUNT_SEARCH = "docsui_search"; + private static final String COUNT_MENU_ACTION = "docsui_menu_action"; + private static final String COUNT_KEYBOARD_ACTION = "docsui_keyboard_action"; // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any // root that is not explicitly recognized by the Metrics code (see {@link @@ -196,8 +201,71 @@ public final class Metrics { @Retention(RetentionPolicy.SOURCE) public @interface MetricsOpType {} - // Codes representing different launch actions. These are used for bucketing stats in the - // COUNT_LAUNCH_ACTION histogram. + // Codes representing different provider types. Used for sorting file operations when logging. + private static final int PROVIDER_INTRA = 0; + private static final int PROVIDER_SYSTEM = 1; + private static final int PROVIDER_EXTERNAL = 2; + + @IntDef(flag = false, value = { + PROVIDER_INTRA, + PROVIDER_SYSTEM, + PROVIDER_EXTERNAL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Provider {} + + + // Codes representing different menu actions. These are used for bucketing stats in the + // COUNT_MENU_ACTION histogram. + // Both regular toolbar menu and action mode menu operations are included. + // Do not change or rearrange these values, that will break historical data. Only add to the + // list. + // Do not use negative numbers or zero; clearcut only handles positive integers. + private static final int ACTION_MENU_OTHER = 1; + private static final int ACTION_MENU_GRID = 2; + private static final int ACTION_MENU_LIST = 3; + private static final int ACTION_MENU_SORT = 4; + private static final int ACTION_MENU_SORT_NAME = 5; + private static final int ACTION_MENU_SORT_DATE = 6; + private static final int ACTION_MENU_SORT_SIZE = 7; + private static final int ACTION_MENU_SEARCH = 8; + private static final int ACTION_MENU_SHOW_SIZE = 9; + private static final int ACTION_MENU_SETTINGS = 10; + private static final int ACTION_MENU_COPY_TO = 11; + private static final int ACTION_MENU_MOVE_TO = 12; + private static final int ACTION_MENU_DELETE = 13; + private static final int ACTION_MENU_RENAME = 14; + private static final int ACTION_MENU_CREATE_DIR = 15; + private static final int ACTION_MENU_SELECT_ALL = 16; + private static final int ACTION_MENU_SHARE = 17; + private static final int ACTION_MENU_OPEN = 18; + private static final int ACTION_MENU_ADVANCED = 19; + + @IntDef(flag = false, value = { + ACTION_MENU_OTHER, + ACTION_MENU_GRID, + ACTION_MENU_LIST, + ACTION_MENU_SORT, + ACTION_MENU_SORT_NAME, + ACTION_MENU_SORT_DATE, + ACTION_MENU_SORT_SIZE, + ACTION_MENU_SHOW_SIZE, + ACTION_MENU_SETTINGS, + ACTION_MENU_COPY_TO, + ACTION_MENU_MOVE_TO, + ACTION_MENU_DELETE, + ACTION_MENU_RENAME, + ACTION_MENU_CREATE_DIR, + ACTION_MENU_SELECT_ALL, + ACTION_MENU_SHARE, + ACTION_MENU_OPEN, + ACTION_MENU_ADVANCED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MenuAction {} + + // Codes representing different menu actions. These are used for bucketing stats in the + // COUNT_MENU_ACTION histogram. // Do not change or rearrange these values, that will break historical data. Only add to the // list. // Do not use negative numbers or zero; clearcut only handles positive integers. @@ -223,18 +291,30 @@ public final class Metrics { @Retention(RetentionPolicy.SOURCE) public @interface MetricsAction {} - // Codes representing different provider types. Used for sorting file operations when logging. - private static final int PROVIDER_INTRA = 0; - private static final int PROVIDER_SYSTEM = 1; - private static final int PROVIDER_EXTERNAL = 2; - - @IntDef(flag = true, value = { - PROVIDER_INTRA, - PROVIDER_SYSTEM, - PROVIDER_EXTERNAL + // Codes representing different keyboard shortcut triggered actions. These are used for + // bucketing stats in the COUNT_KEYBOARD_ACTION histogram. + // Do not change or rearrange these values, that will break historical data. Only add to the + // list. + // Do not use negative numbers or zero; clearcut only handles positive integers. + private static final int ACTION_KEYBOARD_OTHER = 1; + private static final int ACTION_KEYBOARD_PASTE = 2; + private static final int ACTION_KEYBOARD_COPY = 3; + private static final int ACTION_KEYBOARD_DELETE = 4; + private static final int ACTION_KEYBOARD_SELECT_ALL = 5; + private static final int ACTION_KEYBOARD_BACK = 6; + private static final int ACTION_KEYBOARD_SWITCH_FOCUS = 7; + + @IntDef(flag = false, value = { + ACTION_KEYBOARD_OTHER, + ACTION_KEYBOARD_PASTE, + ACTION_KEYBOARD_COPY, + ACTION_KEYBOARD_DELETE, + ACTION_KEYBOARD_SELECT_ALL, + ACTION_KEYBOARD_BACK, + ACTION_KEYBOARD_SWITCH_FOCUS }) @Retention(RetentionPolicy.SOURCE) - public @interface Provider {} + public @interface KeyboardAction {} // Codes representing different actions to open the drawer. They are used for bucketing stats in // the COUNT_DRAWER_OPENED histogram. @@ -440,6 +520,39 @@ public final class Metrics { } /** + * Logs keyboard shortcut actions. Since keyboard shortcuts have their corresponding menu items, + * they are identified by menu item resource id for convenience. + * @param context + * @param keyCode + */ + public static void logKeyboardAction(Context context, int keyCode) { + @KeyboardAction int keyboardAction = ACTION_KEYBOARD_OTHER; + switch (keyCode) { + case KeyEvent.KEYCODE_V: + keyboardAction = ACTION_KEYBOARD_PASTE; + break; + case KeyEvent.KEYCODE_C: + keyboardAction = ACTION_KEYBOARD_COPY; + break; + case KeyEvent.KEYCODE_FORWARD_DEL: + keyboardAction = ACTION_KEYBOARD_DELETE; + break; + case KeyEvent.KEYCODE_A: + keyboardAction = ACTION_KEYBOARD_SELECT_ALL; + break; + case KeyEvent.KEYCODE_DEL: + keyboardAction = ACTION_KEYBOARD_BACK; + break; + case KeyEvent.KEYCODE_TAB: + keyboardAction = ACTION_KEYBOARD_SWITCH_FOCUS; + break; + default: + break; + } + logHistogram(context, COUNT_KEYBOARD_ACTION, keyboardAction); + } + + /** * Logs startup time in milliseconds. * @param context * @param startupMs Startup time in milliseconds. @@ -448,6 +561,25 @@ public final class Metrics { logHistogram(context, COUNT_STARTUP_MS, startupMs); } + /** + * Logs a drag and drop action. Call this when the user drops the content triggering copy. + * operation. + * + * @param context + */ + public static void logDragNDrop(Context context) { + logCount(context, COUNT_DRAG_N_DROP); + } + + /** + * Logs a search. Call this when the search operation is finished. + * + * @param context + */ + public static void logSearch(Context context) { + logCount(context, COUNT_SEARCH); + } + private static void logInterProviderFileOps( Context context, String histogram, @@ -568,6 +700,74 @@ public final class Metrics { } /** + * Logs menu action that was selected by user. + * @param context + * @param id Resource id of the menu item. + */ + public static void logMenuAction(Context context, int id) { + @MenuAction int menuAction = ACTION_MENU_OTHER; + switch (id) { + case R.id.menu_grid: + menuAction = ACTION_MENU_GRID; + break; + case R.id.menu_list: + menuAction = ACTION_MENU_LIST; + break; + case R.id.menu_sort: + menuAction = ACTION_MENU_SORT; + break; + case R.id.menu_sort_name: + menuAction = ACTION_MENU_SORT_NAME; + break; + case R.id.menu_sort_date: + menuAction = ACTION_MENU_SORT_DATE; + break; + case R.id.menu_sort_size: + menuAction = ACTION_MENU_SORT_SIZE; + break; + case R.id.menu_search: + menuAction = ACTION_MENU_SEARCH; + break; + case R.id.menu_file_size: + menuAction = ACTION_MENU_SHOW_SIZE; + break; + case R.id.menu_settings: + menuAction = ACTION_MENU_SETTINGS; + break; + case R.id.menu_copy_to: + menuAction = ACTION_MENU_COPY_TO; + break; + case R.id.menu_move_to: + menuAction = ACTION_MENU_MOVE_TO; + break; + case R.id.menu_delete: + menuAction = ACTION_MENU_DELETE; + break; + case R.id.menu_rename: + menuAction = ACTION_MENU_RENAME; + break; + case R.id.menu_create_dir: + menuAction = ACTION_MENU_CREATE_DIR; + break; + case R.id.menu_select_all: + menuAction = ACTION_MENU_SELECT_ALL; + break; + case R.id.menu_share: + menuAction = ACTION_MENU_SHARE; + break; + case R.id.menu_open: + menuAction = ACTION_MENU_OPEN; + break; + case R.id.menu_advanced: + menuAction = ACTION_MENU_ADVANCED; + break; + default: + break; + } + logHistogram(context, COUNT_MENU_ACTION, menuAction); + } + + /** * Internal method for making a MetricsLogger.count call. Increments the given counter by 1. * * @param context diff --git a/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java index 11b8891b0b12..945ed3413ab6 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java @@ -185,6 +185,9 @@ final class SearchViewManager implements if(mFullBar) { Menu menu = mActionBar.getMenu(); menu.setGroupVisible(R.id.group_hide_when_searching, false); + } else { + // If search in full-bar mode it will be logged in FilesActivity#onOptionsItemSelected + Metrics.logMenuAction(mActionBar.getContext(), R.id.menu_search); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 73ce0e1ffdc7..20316fff31b4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -85,6 +85,7 @@ import com.android.documentsui.Events; import com.android.documentsui.Events.MotionInputEvent; import com.android.documentsui.Menus; import com.android.documentsui.MessageBar; +import com.android.documentsui.Metrics; import com.android.documentsui.R; import com.android.documentsui.RecentsLoader; import com.android.documentsui.RootsCache; @@ -594,6 +595,7 @@ public class DirectoryFragment extends Fragment @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + Metrics.logMenuAction(getContext(), item.getItemId()); Selection selection = mSelectionManager.getSelection(new Selection()); @@ -1124,6 +1126,7 @@ public class DirectoryFragment extends Fragment if (Objects.equals(src, dst)) { return false; } + Metrics.logDragNDrop(getContext()); copyFromClipData(event.getClipData(), dst); return true; } @@ -1336,6 +1339,7 @@ public class DirectoryFragment extends Fragment // This has to be handled here instead of in a keyboard shortcut, because // keyboard shortcuts all have to be modified with the 'Ctrl' key. if (mSelectionManager.hasSelection()) { + Metrics.logKeyboardAction(getContext(), keyCode); deleteDocuments(mSelectionManager.getSelection()); } // Always handle the key, even if there was nothing to delete. This is a @@ -1622,6 +1626,9 @@ public class DirectoryFragment extends Fragment @Override public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) { if (!isAdded()) return; + if (mSearchMode) { + Metrics.logSearch(getContext()); + } State state = getDisplayState(); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java index 454221a6cf42..2b549f1a7fac 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityModel.java @@ -77,6 +77,7 @@ public class KeyguardSecurityModel { case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: + case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: return SecurityMode.Password; case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: diff --git a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java index af4c34795917..0feda92322d0 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/renderer/PdfManipulationService.java @@ -244,9 +244,20 @@ public final class PdfManipulationService extends Service { ranges = PageRangeUtils.normalize(ranges); + int lastPageIdx = mEditor.getPageCount() - 1; + final int rangeCount = ranges.length; for (int i = rangeCount - 1; i >= 0; i--) { PageRange range = ranges[i]; + + // Ignore removal of pages that are outside the document + if (range.getEnd() > lastPageIdx) { + if (range.getStart() > lastPageIdx) { + continue; + } + range = new PageRange(range.getStart(), lastPageIdx); + } + for (int j = range.getEnd(); j >= range.getStart(); j--) { mEditor.removePage(j); } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index dba6a8bfbe22..cde0fa3b4896 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -476,7 +476,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private void onPrintDocumentError(String message) { - mProgressMessageController.cancel(); + setState(mProgressMessageController.cancel()); ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY); setState(STATE_UPDATE_FAILED); @@ -502,7 +502,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat Log.i(LOG_TAG, "onUpdateCanceled()"); } - mProgressMessageController.cancel(); + setState(mProgressMessageController.cancel()); ensurePreviewUiShown(); switch (mState) { @@ -524,7 +524,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat Log.i(LOG_TAG, "onUpdateCompleted()"); } - mProgressMessageController.cancel(); + setState(mProgressMessageController.cancel()); ensurePreviewUiShown(); // Update the print job with the info for the written document. The page @@ -572,7 +572,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat Log.i(LOG_TAG, "onUpdateFailed()"); } - mProgressMessageController.cancel(); + setState(mProgressMessageController.cancel()); ensureErrorUiShown(error, PrintErrorFragment.ACTION_RETRY); if (mState == STATE_CREATE_FILE_FAILED @@ -1169,6 +1169,18 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat doFinish(); } + /** + * Update the selected pages from the text field. + */ + private void updateSelectedPagesFromTextField() { + PageRange[] selectedPages = computeSelectedPages(); + if (!Arrays.equals(mSelectedPages, selectedPages)) { + mSelectedPages = selectedPages; + // Update preview. + updatePrintPreviewController(false); + } + } + private void confirmPrint() { setState(STATE_PRINT_CONFIRMED); @@ -1178,14 +1190,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat addCurrentPrinterToHistory(); setUserPrinted(); - PageRange[] selectedPages = computeSelectedPages(); - if (!Arrays.equals(mSelectedPages, selectedPages)) { - mSelectedPages = selectedPages; - // Update preview. - updatePrintPreviewController(false); - } - + // updateSelectedPagesFromTextField migth update the preview, hence apply the preview first updateSelectedPagesFromPreview(); + updateSelectedPagesFromTextField(); + mPrintPreviewController.closeOptions(); if (canUpdateDocument()) { @@ -1485,6 +1493,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat cancelPrint(); } } else if (view == mMoreOptionsButton) { + // The selected pages is only applied once the user leaves the text field. A click + // on this button, does not count as leaving. + updateSelectedPagesFromTextField(); + if (mCurrentPrinter != null) { startAdvancedPrintOptionsActivity(mCurrentPrinter); } @@ -2066,8 +2078,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat mSpoolerProvider.destroy(); } + setState(mProgressMessageController.cancel()); + if (mState != STATE_INITIALIZING) { - mProgressMessageController.cancel(); mPrintedDocument.finish(); mPrintedDocument.destroy(); mPrintPreviewController.destroy(new Runnable() { @@ -2773,13 +2786,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } if (view == mPageRangeEditText && !hasFocus) { - PageRange[] selectedPages = computeSelectedPages(); - if (selectedPages != null && !Arrays.equals(mSelectedPages, selectedPages)) { - mSelectedPages = selectedPages; - - // Update preview. - updatePrintPreviewController(false); - } + updateSelectedPagesFromTextField(); } } } @@ -2910,29 +2917,50 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private boolean mPosted; + /** State before run was executed */ + private int mPreviousState = -1; + public ProgressMessageController(Context context) { mHandler = new Handler(context.getMainLooper(), null, false); } public void post() { - if (mPosted) { + if (mState == STATE_UPDATE_SLOW) { + setState(STATE_UPDATE_SLOW); + ensureProgressUiShown(); + updateOptionsUi(); + + return; + } else if (mPosted) { return; } + mPreviousState = -1; mPosted = true; mHandler.postDelayed(this, PROGRESS_TIMEOUT_MILLIS); } - public void cancel() { + private int getStateAfterCancel() { + if (mPreviousState == -1) { + return mState; + } else { + return mPreviousState; + } + } + + public int cancel() { if (!mPosted) { - return; + return getStateAfterCancel(); } mPosted = false; mHandler.removeCallbacks(this); + + return getStateAfterCancel(); } @Override public void run() { mPosted = false; + mPreviousState = mState; setState(STATE_UPDATE_SLOW); ensureProgressUiShown(); updateOptionsUi(); @@ -3081,12 +3109,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat List<PageRange> rangesToShred = new ArrayList<>(); PageRange previousRange = null; - final int pageCount = printJob.getDocumentInfo().getPageCount(); - PageRange[] printedPages = printJob.getPages(); final int rangeCount = printedPages.length; for (int i = 0; i < rangeCount; i++) { - PageRange range = PageRangeUtils.asAbsoluteRange(printedPages[i], pageCount); + PageRange range = printedPages[i]; if (previousRange == null) { final int startPageIdx = 0; @@ -3105,11 +3131,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } if (i == rangeCount - 1) { - final int startPageIdx = range.getEnd() + 1; - final int endPageIdx = printJob.getDocumentInfo().getPageCount() - 1; - if (startPageIdx <= endPageIdx) { - PageRange removedRange = new PageRange(startPageIdx, endPageIdx); - rangesToShred.add(removedRange); + if (range.getEnd() != Integer.MAX_VALUE) { + rangesToShred.add(new PageRange(range.getEnd() + 1, Integer.MAX_VALUE)); } } diff --git a/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml b/packages/SystemUI/res/anim/tv_pip_controls_buttons_in_recents_focus_gain_animation.xml index 89e4aac8dae5..ebc6a4a74a85 100644 --- a/packages/SystemUI/res/anim/tv_pip_controls_fade_in.xml +++ b/packages/SystemUI/res/anim/tv_pip_controls_buttons_in_recents_focus_gain_animation.xml @@ -17,13 +17,13 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator - android:propertyName="translationY" - android:valueTo="10dp" - android:interpolator="@android:interpolator/linear_out_slow_in" + android:propertyName="scaleX" + android:valueTo="1.0" + android:interpolator="@android:interpolator/fast_out_slow_in" android:duration="@integer/recents_tv_pip_focus_anim_duration" /> <objectAnimator - android:propertyName="alpha" + android:propertyName="scaleY" android:valueTo="1.0" - android:interpolator="@android:interpolator/linear_out_slow_in" + android:interpolator="@android:interpolator/fast_out_slow_in" android:duration="@integer/recents_tv_pip_focus_anim_duration" /> </set> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_fade_out.xml b/packages/SystemUI/res/anim/tv_pip_controls_buttons_in_recents_focus_lose_animation.xml index c73fed6d4892..95499bd38977 100644 --- a/packages/SystemUI/res/anim/tv_pip_controls_fade_out.xml +++ b/packages/SystemUI/res/anim/tv_pip_controls_buttons_in_recents_focus_lose_animation.xml @@ -17,13 +17,13 @@ <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator - android:propertyName="translationY" - android:valueTo="0dp" - android:interpolator="@android:interpolator/fast_out_linear_in" + android:propertyName="scaleX" + android:valueTo="0.7" + android:interpolator="@android:interpolator/fast_out_slow_in" android:duration="@integer/recents_tv_pip_focus_anim_duration" /> <objectAnimator - android:propertyName="alpha" - android:valueTo="0.0" - android:interpolator="@android:interpolator/fast_out_linear_in" + android:propertyName="scaleY" + android:valueTo="0.7" + android:interpolator="@android:interpolator/fast_out_slow_in" android:duration="@integer/recents_tv_pip_focus_anim_duration" /> </set> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml new file mode 100644 index 000000000000..7555bdda8b64 --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="translationY" + android:valueTo="0dp" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="@integer/recents_tv_pip_focus_anim_duration" /> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_lose_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_lose_animation.xml new file mode 100644 index 000000000000..b40ccd467bec --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_lose_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="translationY" + android:valueTo="-57dp" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="@integer/recents_tv_pip_focus_anim_duration" /> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_gain_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_gain_animation.xml new file mode 100644 index 000000000000..681ff917b646 --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_gain_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="alpha" + android:valueTo="1" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="@integer/recents_tv_pip_focus_anim_duration" /> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_lose_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_lose_animation.xml new file mode 100644 index 000000000000..e6deb0f0bfdc --- /dev/null +++ b/packages/SystemUI/res/anim/tv_pip_controls_text_in_recents_focus_lose_animation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" + android:propertyName="alpha" + android:valueTo="0" + android:interpolator="@android:interpolator/fast_out_slow_in" + android:duration="@integer/recents_tv_pip_focus_anim_duration" /> diff --git a/packages/SystemUI/res/drawable-hdpi/stat_notify_image.png b/packages/SystemUI/res/drawable-hdpi/stat_notify_image.png Binary files differdeleted file mode 100644 index 7b0fcc7c104e..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/stat_notify_image.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/stat_notify_image_error.png b/packages/SystemUI/res/drawable-hdpi/stat_notify_image_error.png Binary files differdeleted file mode 100644 index 73e9c96d36e4..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/stat_notify_image_error.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/stat_notify_image.png b/packages/SystemUI/res/drawable-mdpi/stat_notify_image.png Binary files differdeleted file mode 100644 index a02e21ccf3bf..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/stat_notify_image.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/stat_notify_image_error.png b/packages/SystemUI/res/drawable-mdpi/stat_notify_image_error.png Binary files differdeleted file mode 100644 index 4af2617ae72e..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/stat_notify_image_error.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_notify_image.png b/packages/SystemUI/res/drawable-xhdpi/stat_notify_image.png Binary files differdeleted file mode 100644 index 24bdbb683a6f..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/stat_notify_image.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_notify_image_error.png b/packages/SystemUI/res/drawable-xhdpi/stat_notify_image_error.png Binary files differdeleted file mode 100644 index 6ecd2d3e5779..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/stat_notify_image_error.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image.png b/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image.png Binary files differdeleted file mode 100644 index 5e733ef4116e..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image_error.png b/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image_error.png Binary files differdeleted file mode 100644 index ecc2c833e2e2..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/stat_notify_image_error.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable/stat_notify_image.xml b/packages/SystemUI/res/drawable/stat_notify_image.xml new file mode 100644 index 000000000000..c8745d7d046c --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_notify_image.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M21,19V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14C20.1,21,21,20.1,21,19z +M8.5,13.5l2.5,3l3.5-4.5l4.5,6H5 L8.5,13.5z" /> + <path + android:pathData="M0,0h24v24H0V0z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/stat_notify_image_error.xml b/packages/SystemUI/res/drawable/stat_notify_image_error.xml new file mode 100644 index 000000000000..b9290051a32b --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_notify_image_error.xml @@ -0,0 +1,30 @@ +<!-- +Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:pathData="M0,0h24v24H0V0z" /> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M17,18H5l3.5-4.5l2.5,3l3.3-4.5l2.7,3.8V8h4V5c0-1.1-0.9-2-2-2H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h12V18z" /> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M19.2,21H21v-1.8h-1.8V21z M19.2,9.9v7.4H21V9.9H19.2z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/tv_pip_button_focused.xml b/packages/SystemUI/res/drawable/tv_pip_button_focused.xml index 5cabb77a6971..405ea0cb9fbc 100644 --- a/packages/SystemUI/res/drawable/tv_pip_button_focused.xml +++ b/packages/SystemUI/res/drawable/tv_pip_button_focused.xml @@ -17,8 +17,8 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <size - android:width="36dp" - android:height="36dp" /> + android:width="34dp" + android:height="34dp" /> <solid android:color="#4DFFFFFF" /> </shape> diff --git a/packages/SystemUI/res/layout/recents_on_tv.xml b/packages/SystemUI/res/layout/recents_on_tv.xml index 0f8c77c3042b..28ea66d8345d 100644 --- a/packages/SystemUI/res/layout/recents_on_tv.xml +++ b/packages/SystemUI/res/layout/recents_on_tv.xml @@ -31,18 +31,12 @@ android:focusable="true" android:layoutDirection="rtl" /> + <!-- Placeholder view to give focus to the PIP menus. --> <View - android:id="@+id/pip_shade" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" - android:background="#76000000" /> - - <include - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|center_horizontal" - android:layout_marginTop="132dp" - layout="@layout/tv_pip_controls" /> + android:id="@+id/pip" + android:layout_width="0dp" + android:layout_height="0dp" + android:focusable="true" + android:visibility="gone" /> </com.android.systemui.recents.tv.views.RecentsTvView> diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml index 2e0c9e76c504..563441ff3e35 100644 --- a/packages/SystemUI/res/layout/tv_pip_controls.xml +++ b/packages/SystemUI/res/layout/tv_pip_controls.xml @@ -17,13 +17,8 @@ */ --> -<com.android.systemui.tv.pip.PipControlsView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/pip_controls" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal"> +<!-- Layout for {@link com.android.systemui.tv.pip.PipControlsView}. --> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="100dp" @@ -98,4 +93,4 @@ android:textSize="12sp" android:textColor="#EEEEEE" /> </LinearLayout> -</com.android.systemui.tv.pip.PipControlsView> +</merge> diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml index c2c83ffb9d62..2647a99632c1 100644 --- a/packages/SystemUI/res/layout/tv_pip_menu.xml +++ b/packages/SystemUI/res/layout/tv_pip_menu.xml @@ -26,7 +26,8 @@ android:gravity="top|center_horizontal" android:clipChildren="false"> - <include - layout="@layout/tv_pip_controls" - android:clipChildren="false" /> + <com.android.systemui.tv.pip.PipControlsView + android:id="@+id/pip_controls" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml index c5c7e843208b..64bf3b5bbdbf 100644 --- a/packages/SystemUI/res/layout/tv_pip_overlay.xml +++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml @@ -38,25 +38,4 @@ android:gravity="center" android:maxLines="2" android:text="@string/pip_hold_home" /> - <LinearLayout - android:id="@+id/guide_buttons" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_centerHorizontal="true" - android:orientation="horizontal"> - <ImageView - android:layout_width="19dp" - android:layout_height="19dp" - android:src="@drawable/ic_fullscreen_white_24dp" /> - <ImageView - android:layout_width="19dp" - android:layout_height="19dp" - android:src="@drawable/ic_close_white" /> - <ImageView - android:id="@+id/guide_button_play_pause" - android:layout_width="19dp" - android:layout_height="19dp" - android:src="@drawable/ic_pause_white_24dp" /> - </LinearLayout> </RelativeLayout> diff --git a/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml b/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml new file mode 100644 index 000000000000..1e464d83ebdd --- /dev/null +++ b/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="top|center_horizontal"> + + <com.android.systemui.tv.pip.PipRecentsControlsView + android:id="@+id/pip_controls" + android:layout_width="wrap_content" + android:layout_height="match_parent" /> + + <View + android:id="@+id/recents" + android:layout_width="1dp" + android:layout_height="1dp" + android:focusable="true" /> +</LinearLayout> diff --git a/packages/SystemUI/res/values/dimens_tv.xml b/packages/SystemUI/res/values/dimens_tv.xml index 953dd650b2aa..337513d4daea 100644 --- a/packages/SystemUI/res/values/dimens_tv.xml +++ b/packages/SystemUI/res/values/dimens_tv.xml @@ -38,9 +38,6 @@ <dimen name="recents_tv_unselected_item_z">6dp</dimen> <dimen name="recents_tv_selected_item_z_delta">10dp</dimen> - <!-- Extra space around the PIP and its outline in PIP onboarding activity --> - <dimen name="tv_pip_bounds_space">3dp</dimen> - <!-- Values for text on recents cards on tv --> <dimen name="recents_tv_title_text_size">12sp</dimen> @@ -52,4 +49,10 @@ <dimen name="recents_tv_dismiss_icon_bottom_margin">1dip</dimen> <dimen name="recents_tv_dismiss_text_size">12sp</dimen> + <!-- Values for PIP in recents --> + <dimen name="recents_tv_pip_controls_margin_top">10dp</dimen> + + <!-- Extra space around the PIP and its outline in PIP onboarding activity --> + <dimen name="tv_pip_bounds_space">3dp</dimen> + </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a523a411399f..a01066d70262 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1388,6 +1388,31 @@ <string name="keyboard_shortcut_group_system_recents">Recents</string> <!-- User visible title for the the keyboard shortcut that triggers the back action. --> <string name="keyboard_shortcut_group_system_back">Back</string> + <!-- User visible title for the the keyboard shortcut that triggers the notification shade. --> + <string name="keyboard_shortcut_group_system_notifications">Notifications</string> + <!-- User visible title for the the keyboard shortcut that triggers the keyboard shortcuts helper. --> + <string name="keyboard_shortcut_group_system_shortcuts_helper">Keyboard Shortcuts</string> + <!-- User visible title for the the keyboard shortcut that switches input methods. --> + <string name="keyboard_shortcut_group_system_switch_input">Switch input method</string> + + <!-- User visible title for the system-wide applications keyboard shortcuts list. --> + <string name="keyboard_shortcut_group_applications">Applications</string> + <!-- User visible title for the keyboard shortcut that takes the user to the assist app. --> + <string name="keyboard_shortcut_group_applications_assist">Assist</string> + <!-- User visible title for the keyboard shortcut that takes the user to the browser app. --> + <string name="keyboard_shortcut_group_applications_browser">Browser</string> + <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. --> + <string name="keyboard_shortcut_group_applications_contacts">Contacts</string> + <!-- User visible title for the keyboard shortcut that takes the user to the email app. --> + <string name="keyboard_shortcut_group_applications_email">Email</string> + <!-- User visible title for the keyboard shortcut that takes the user to the instant messaging app. --> + <string name="keyboard_shortcut_group_applications_im">IM</string> + <!-- User visible title for the keyboard shortcut that takes the user to the music app. --> + <string name="keyboard_shortcut_group_applications_music">Music</string> + <!-- User visible title for the keyboard shortcut that takes the user to the YouTube app. --> + <string name="keyboard_shortcut_group_applications_youtube">YouTube</string> + <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. --> + <string name="keyboard_shortcut_group_applications_calendar">Calendar</string> <!-- SysUI Tuner: Option to show full do not disturb panel in volume [CHAR LIMIT=60] --> <string name="tuner_full_zen_title">Show with volume controls</string> diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index fff7d78d0078..8060e07801bb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -344,7 +344,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // Update the nav bar scrim, but defer the animation until the enter-window event boolean animateNavBarScrim = !launchState.launchedViaDockGesture; - updateNavBarScrim(animateNavBarScrim, null); + mScrimViews.updateNavBarScrim(animateNavBarScrim, stack.getTaskCount() > 0, null); // If this is a new instance relaunched by AM, without going through the normal mechanisms, // then we have to manually trigger the enter animation state @@ -417,39 +417,43 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - // Update the nav bar for the current orientation - updateNavBarScrim(false /* animateNavBarScrim */, AnimationProps.IMMEDIATE); // Notify of the config change int newOrientation = getResources().getConfiguration().orientation; + int numStackTasks = mRecentsView.getStack().getStackTaskCount(); EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */, - (mLastOrientation != newOrientation))); + (mLastOrientation != newOrientation), numStackTasks > 0)); mLastOrientation = newOrientation; } @Override public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { super.onMultiWindowModeChanged(isInMultiWindowMode); + + // Reload the task stack completely + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); + RecentsTaskLoader loader = Recents.getTaskLoader(); + RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this); + loader.preloadTasks(loadPlan, -1 /* topTaskId */, false /* isTopTaskHome */); + + RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); + loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks; + loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; + loader.loadTasks(this, loadPlan, loadOpts); + + TaskStack stack = loadPlan.getTaskStack(); + int numStackTasks = stack.getStackTaskCount(); + EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */, - false /* fromOrientationChange */)); + false /* fromOrientationChange */, numStackTasks > 0)); if (mRecentsView != null) { - // Reload the task stack completely - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this); - loader.preloadTasks(loadPlan, -1 /* topTaskId */, false /* isTopTaskHome */); - - RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); - loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks; - loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; - loader.loadTasks(this, loadPlan, loadOpts); - - mRecentsView.updateStack(loadPlan.getTaskStack()); + mRecentsView.updateStack(stack); } - EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode)); + EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode, + numStackTasks > 0)); } @Override @@ -729,18 +733,4 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD }); return true; } - - /** - * Updates the nav bar scrim. - */ - private void updateNavBarScrim(boolean animateNavBarScrim, AnimationProps animation) { - // Animate the SystemUI scrims into view - SystemServicesProxy ssp = Recents.getSystemServices(); - int taskCount = mRecentsView.getStack().getTaskCount(); - boolean hasNavBarScrim = (taskCount > 0) && !ssp.hasTransposedNavBar(); - mScrimViews.prepareEnterRecentsAnimation(hasNavBarScrim, animateNavBarScrim); - if (animateNavBarScrim && animation != null) { - mScrimViews.animateNavBarScrimVisibility(true, animation); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 43d627d9e13a..4dae7460f30a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -584,18 +584,24 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener Rect systemInsets = new Rect(); ssp.getStableInsets(systemInsets); Rect windowRect = ssp.getWindowRect(); + // When docked, the nav bar insets are consumed and the activity is measured without insets. + // However, the window bounds include the insets, so we need to subtract them here to make + // them identical. + if (ssp.hasDockedTask()) { + windowRect.bottom -= systemInsets.bottom; + systemInsets.bottom = 0; + } calculateWindowStableInsets(systemInsets, windowRect); windowRect.offsetTo(0, 0); TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm(); - stackLayout.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, - mTaskStackBounds); // Rebind the header bar and draw it for the transition - Rect taskStackBounds = new Rect(mTaskStackBounds); stackLayout.setSystemInsets(systemInsets); if (stack != null) { - stackLayout.initialize(windowRect, taskStackBounds, + stackLayout.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, + mTaskStackBounds); + stackLayout.initialize(windowRect, mTaskStackBounds, TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack)); mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */); } @@ -794,6 +800,9 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener synchronized (mHeaderBarLock) { int toHeaderWidth = (int) toTransform.rect.width(); int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); + if (toHeaderWidth <= 0 || toHeaderHeight <= 0) { + return null; + } boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode(); mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(), (int) toTransform.rect.height()); diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java index 8be9ca7b5f2b..e3bc2a760b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java @@ -25,9 +25,12 @@ public class ConfigurationChangedEvent extends EventBus.AnimatedEvent { public final boolean fromMultiWindow; public final boolean fromOrientationChange; + public final boolean hasStackTasks; - public ConfigurationChangedEvent(boolean fromMultiWindow, boolean fromOrientationChange) { + public ConfigurationChangedEvent(boolean fromMultiWindow, boolean fromOrientationChange, + boolean hasStackTasks) { this.fromMultiWindow = fromMultiWindow; this.fromOrientationChange = fromOrientationChange; + this.hasStackTasks = hasStackTasks; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java index 19245d9534df..cf2a68e2cf95 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java @@ -24,8 +24,10 @@ import com.android.systemui.recents.events.EventBus; public class MultiWindowStateChangedEvent extends EventBus.Event { public final boolean inMultiWindow; + public final boolean hasStackTasks; - public MultiWindowStateChangedEvent(boolean inMultiWindow) { + public MultiWindowStateChangedEvent(boolean inMultiWindow, boolean hasStackTasks) { this.inMultiWindow = inMultiWindow; + this.hasStackTasks = hasStackTasks; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java index 134b90c99fff..483f9e523c55 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java @@ -15,8 +15,6 @@ */ package com.android.systemui.recents.tv; -import android.animation.AnimatorInflater; -import android.animation.AnimatorSet; import android.app.Activity; import android.app.ActivityOptions; import android.content.Intent; @@ -55,11 +53,12 @@ import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.tv.animations.FocusAnimationHolder; import com.android.systemui.recents.tv.views.RecentsTvView; import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.tv.pip.PipManager; -import com.android.systemui.tv.pip.PipControlsView; +import com.android.systemui.tv.pip.PipRecentsOverlayManager; import java.util.ArrayList; import java.util.Collections; @@ -80,15 +79,13 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { private boolean mIgnoreAltTabRelease; private RecentsTvView mRecentsView; - private PipControlsView mPipControlsView; - private View mPipShadeView; - private AnimatorSet mPipControlsViewFadeInAnimator; - private AnimatorSet mPipControlsViewFadeOutAnimator; + private FocusAnimationHolder mRecentsFocusAnimationHolder; + private View mPipView; private TaskStackHorizontalViewAdapter mTaskStackViewAdapter; private FinishRecentsRunnable mFinishLaunchHomeRunnable; - private PipManager mPipManager; - private PipManager.Listener mPipListener = new PipManager.Listener() { + private final PipManager mPipManager = PipManager.getInstance(); + private final PipManager.Listener mPipListener = new PipManager.Listener() { @Override public void onPipEntered() { updatePipUI(); @@ -113,10 +110,38 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { @Override public void onPipResizeAboutToStart() { } - - @Override - public void onMediaControllerChanged() { } }; + private PipRecentsOverlayManager mPipRecentsOverlayManager; + private final PipRecentsOverlayManager.Callback mPipRecentsOverlayManagerCallback = + new PipRecentsOverlayManager.Callback() { + @Override + public void onClosed() { + dismissRecentsToLaunchTargetTaskOrHome(); + } + + @Override + public void onBackPressed() { + RecentsTvActivity.this.onBackPressed(); + } + + @Override + public void onRecentsFocused() { + mRecentsView.requestFocus(); + } + }; + private final View.OnFocusChangeListener mPipViewFocusChangeListener = + new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + mRecentsFocusAnimationHolder.startFocusLoseAnimation(); + mPipRecentsOverlayManager.requestFocus( + mTaskStackViewAdapter.getItemCount() > 0); + } else { + mRecentsFocusAnimationHolder.startFocusGainAnimation(); + } + } + }; /** * A common Runnable to finish Recents by launching Home with an animation depending on the @@ -248,7 +273,7 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { finish(); return; } - mPipManager = PipManager.getInstance(); + mPipRecentsOverlayManager = PipManager.getInstance().getPipRecentsOverlayManager(); // Register this activity with the event bus EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); @@ -263,21 +288,19 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - mPipControlsView = (PipControlsView) findViewById(R.id.pip_controls); - mPipControlsView.setListener(new PipControlsView.Listener() { - @Override - public void onClosed() { - dismissRecentsToLaunchTargetTaskOrHome(); - } - }); - mPipShadeView = findViewById(R.id.pip_shade); + mRecentsFocusAnimationHolder = new FocusAnimationHolder(mRecentsView); + + mPipView = findViewById(R.id.pip); + // Place mPipView at the PIP bounds for fine tuned focus handling. + Rect pipBounds = mPipManager.getPipBounds(); + LayoutParams lp = (LayoutParams) mPipView.getLayoutParams(); + lp.width = pipBounds.width(); + lp.height = pipBounds.height(); + lp.leftMargin = pipBounds.left; + lp.topMargin = pipBounds.top; + mPipView.setLayoutParams(lp); - mPipControlsViewFadeInAnimator = (AnimatorSet) AnimatorInflater.loadAnimator(this, - R.anim.tv_pip_controls_fade_in); - mPipControlsViewFadeInAnimator.setTarget(mPipControlsView); - mPipControlsViewFadeOutAnimator = (AnimatorSet) AnimatorInflater.loadAnimator(this, - R.anim.tv_pip_controls_fade_out); - mPipControlsViewFadeOutAnimator.setTarget(mPipControlsView); + mPipRecentsOverlayManager.setCallback(mPipRecentsOverlayManagerCallback); getWindow().getAttributes().privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; @@ -289,7 +312,6 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent); - updatePipUI(); mPipManager.addListener(mPipListener); } @@ -321,9 +343,7 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { SystemServicesProxy ssp = Recents.getSystemServices(); EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true)); - mPipManager.onRecentsStarted(); - // Give focus to the recents row whenever its visible to an user. - mRecentsView.requestFocus(); + updatePipUI(); } @Override @@ -333,10 +353,21 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { } @Override + public void onResume() { + super.onResume(); + mPipRecentsOverlayManager.onRecentsResumed(); + } + + @Override + public void onPause() { + super.onPause(); + mPipRecentsOverlayManager.onRecentsPaused(); + } + + @Override protected void onStop() { super.onStop(); - mPipManager.onRecentsStopped(); mIgnoreAltTabRelease = false; // Notify that recents is now hidden EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false)); @@ -480,25 +511,13 @@ public class RecentsTvActivity extends Activity implements OnPreDrawListener { private void updatePipUI() { if (mPipManager.isPipShown()) { - mPipControlsView.setAlpha(0); - mPipControlsView.setVisibility(View.VISIBLE); - mPipShadeView.setVisibility(View.INVISIBLE); - mPipControlsView.setOnChildFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPipManager.onPipViewFocusChangedInRecents(hasFocus); - if (hasFocus) { - mPipControlsViewFadeInAnimator.start(); - } else { - mPipControlsViewFadeOutAnimator.start(); - } - mPipShadeView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); - } - }); - mPipShadeView.setVisibility(View.GONE); + mPipView.setVisibility(View.VISIBLE); + mPipView.setOnFocusChangeListener(mPipViewFocusChangeListener); + mPipView.requestFocus(); } else { - mPipControlsView.setVisibility(View.GONE); - mPipShadeView.setVisibility(View.GONE); + mPipView.setVisibility(View.GONE); + mPipRecentsOverlayManager.removePipRecentsOverlayView(); + mRecentsFocusAnimationHolder.reset(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/FocusAnimationHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/FocusAnimationHolder.java new file mode 100644 index 000000000000..864540c616e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/FocusAnimationHolder.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.recents.tv.animations; + +import android.content.res.Resources; +import android.view.View; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.recents.tv.views.TaskCardView; + +/** + * Collections of Recents row's animation depending on the PIP's focus. + */ +public class FocusAnimationHolder { + private final float DIM_ALPHA = 0.5f; + + private View mRecentsRowView; + private int mCardYDelta; + private long mDuration; + + public FocusAnimationHolder(View recentsRowView) { + mRecentsRowView = recentsRowView; + + Resources res = recentsRowView.getResources(); + mCardYDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_shift_down); + mDuration = res.getInteger(R.integer.recents_tv_pip_focus_anim_duration); + } + + public void startFocusGainAnimation() { + mRecentsRowView.animate() + .setDuration(mDuration) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(1f) + .translationY(0); + } + + public void startFocusLoseAnimation() { + mRecentsRowView.animate() + .setDuration(mDuration) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(DIM_ALPHA) + .translationY(mCardYDelta); + } + + public void reset() { + mRecentsRowView.setTransitionAlpha(1f); + mRecentsRowView.setTranslationY(0); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java index 13ad9a5ee90d..07a1d4edaeaf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java @@ -16,40 +16,58 @@ package com.android.systemui.recents.views; -import android.app.Activity; import android.content.Context; import android.view.View; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsActivity; +import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; +import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; +import com.android.systemui.recents.misc.SystemServicesProxy; /** Manages the scrims for the various system bars. */ public class SystemBarScrimViews { - Context mContext; + private static final int DEFAULT_ANIMATION_DURATION = 150; - View mNavBarScrimView; + private Context mContext; - boolean mHasNavBarScrim; - boolean mShouldAnimateNavBarScrim; + private View mNavBarScrimView; - int mNavBarScrimEnterDuration; + private boolean mHasNavBarScrim; + private boolean mShouldAnimateNavBarScrim; - public SystemBarScrimViews(Activity activity) { + private int mNavBarScrimEnterDuration; + + public SystemBarScrimViews(RecentsActivity activity) { mContext = activity; mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim); + mNavBarScrimView.forceHasOverlappingRendering(false); mNavBarScrimEnterDuration = activity.getResources().getInteger( R.integer.recents_nav_bar_scrim_enter_duration); } /** + * Updates the nav bar scrim. + */ + public void updateNavBarScrim(boolean animateNavBarScrim, boolean hasStackTasks, + AnimationProps animation) { + prepareEnterRecentsAnimation(isNavBarScrimRequired(hasStackTasks), animateNavBarScrim); + if (animateNavBarScrim && animation != null) { + animateNavBarScrimVisibility(true, animation); + } + } + + /** * Prepares the scrim views for animating when entering Recents. This will be called before * the first draw, unless we are updating the scrim on configuration change. */ - public void prepareEnterRecentsAnimation(boolean hasNavBarScrim, boolean animateNavBarScrim) { + private void prepareEnterRecentsAnimation(boolean hasNavBarScrim, boolean animateNavBarScrim) { mHasNavBarScrim = hasNavBarScrim; mShouldAnimateNavBarScrim = animateNavBarScrim; @@ -60,7 +78,7 @@ public class SystemBarScrimViews { /** * Animates the nav bar scrim visibility. */ - public void animateNavBarScrimVisibility(boolean visible, AnimationProps animation) { + private void animateNavBarScrimVisibility(boolean visible, AnimationProps animation) { int toY = 0; if (visible) { mNavBarScrimView.setVisibility(View.VISIBLE); @@ -79,6 +97,14 @@ public class SystemBarScrimViews { } } + /** + * @return Whether to show the nav bar scrim. + */ + private boolean isNavBarScrimRequired(boolean hasStackTasks) { + SystemServicesProxy ssp = Recents.getSystemServices(); + return hasStackTasks && !ssp.hasTransposedNavBar() && !ssp.hasDockedTask(); + } + /**** EventBus events ****/ /** @@ -101,21 +127,48 @@ public class SystemBarScrimViews { */ public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { if (mHasNavBarScrim) { - AnimationProps animation = new AnimationProps() - .setDuration(AnimationProps.BOUNDS, - TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION) - .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN); + AnimationProps animation = createBoundsAnimation( + TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION); animateNavBarScrimVisibility(false, animation); } } public final void onBusEvent(DismissAllTaskViewsEvent event) { if (mHasNavBarScrim) { - AnimationProps animation = new AnimationProps() - .setDuration(AnimationProps.BOUNDS, - TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION) - .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN); + AnimationProps animation = createBoundsAnimation( + TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION); animateNavBarScrimVisibility(false, animation); } } + + public final void onBusEvent(ConfigurationChangedEvent event) { + animateScrimToCurrentNavBarState(event.hasStackTasks); + } + + public final void onBusEvent(MultiWindowStateChangedEvent event) { + animateScrimToCurrentNavBarState(event.hasStackTasks); + } + + /** + * Animates the scrim to match the state of the current nav bar. + */ + private void animateScrimToCurrentNavBarState(boolean hasStackTasks) { + boolean hasNavBarScrim = isNavBarScrimRequired(hasStackTasks); + if (mHasNavBarScrim != hasNavBarScrim) { + AnimationProps animation = hasNavBarScrim + ? createBoundsAnimation(DEFAULT_ANIMATION_DURATION) + : AnimationProps.IMMEDIATE; + animateNavBarScrimVisibility(hasNavBarScrim, animation); + } + mHasNavBarScrim = hasNavBarScrim; + } + + /** + * @return a default animation to aniamte the bounds of the scrim. + */ + private AnimationProps createBoundsAnimation(int duration) { + return new AnimationProps() + .setDuration(AnimationProps.BOUNDS, duration) + .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 25083040df1d..9eab0f60584f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -537,11 +537,13 @@ public class TaskStackLayoutAlgorithm { } else { mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP); } + mInitialNormX = null; } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) { // If there is one stack task, ignore the min/max/initial scroll positions mMinScrollP = 0; mMaxScrollP = 0; mInitialScrollP = 0; + mInitialNormX = null; } else { // Set the max scroll to be the point where the front most task is visible with the // stack bottom offset @@ -803,8 +805,9 @@ public class TaskStackLayoutAlgorithm { public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll, TaskViewTransform transformOut, TaskViewTransform frontTransform) { Rect windowRect = Recents.getSystemServices().getWindowRect(); - TaskViewTransform transform = getStackTransform(task, stackScroll, transformOut, - frontTransform); + TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState, + transformOut, frontTransform, true /* forceUpdate */, + false /* ignoreTaskOverrides */); transform.rect.offset(windowRect.left, windowRect.top); return transform; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java index 3eeabc74801d..e5ac0d31ca8f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -141,9 +141,9 @@ public class TaskViewThumbnail extends View { return; } + int viewWidth = mTaskViewRect.width(); + int viewHeight = mTaskViewRect.height(); if (mBitmapShader != null) { - int viewWidth = mTaskViewRect.width(); - int viewHeight = mTaskViewRect.height(); // We are drawing the thumbnail in the same orientation, so just fit the width int thumbnailWidth = (int) (mThumbnailRect.width() * mThumbnailScale); @@ -180,6 +180,9 @@ public class TaskViewThumbnail extends View { canvas.restoreToCount(count); } + } else { + canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius, + mBgFillPaint); } } @@ -319,12 +322,12 @@ public class TaskViewThumbnail extends View { mDisabledInSafeMode = disabledInSafeMode; if (t.thumbnail != null) { setThumbnail(t.thumbnail, thumbnailInfo); - if (t.colorBackground != 0) { - mBgFillPaint.setColor(t.colorBackground); - } } else { setThumbnail(null, null); } + if (t.colorBackground != 0) { + mBgFillPaint.setColor(t.colorBackground); + } } /** Unbinds the thumbnail view from the task */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 0e21517c3a60..4ed64260dc7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -19,7 +19,9 @@ package com.android.systemui.statusbar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.ActivityManager; +import android.app.ActivityManager.StackId; import android.app.ActivityManagerNative; +import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; @@ -347,7 +349,7 @@ public abstract class BaseStatusBar extends SystemUI implements }, afterKeyguardGone); return true; } else { - return super.onClickHandler(view, pendingIntent, fillInIntent); + return superOnClickHandler(view, pendingIntent, fillInIntent); } } @@ -384,7 +386,8 @@ public abstract class BaseStatusBar extends SystemUI implements private boolean superOnClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { - return super.onClickHandler(view, pendingIntent, fillInIntent); + return super.onClickHandler(view, pendingIntent, fillInIntent, + StackId.FULLSCREEN_WORKSPACE_STACK_ID); } private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) { @@ -990,7 +993,7 @@ public abstract class BaseStatusBar extends SystemUI implements } TaskStackBuilder.create(mContext) .addNextIntentWithParentStack(intent) - .startActivities(null, + .startActivities(getActivityOptions(), new UserHandle(UserHandle.getUserId(appUid))); overrideActivityPendingAppTransition(keyguardShowing); } catch (RemoteException e) { @@ -1740,7 +1743,7 @@ public abstract class BaseStatusBar extends SystemUI implements } catch (RemoteException e) { } try { - intent.send(); + intent.send(null, 0, null, null, null, null, getActivityOptions()); } catch (PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. // Just log the exception message. @@ -1848,7 +1851,8 @@ public abstract class BaseStatusBar extends SystemUI implements } } try { - intent.send(); + intent.send(null, 0, null, null, null, null, + getActivityOptions()); } catch (PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. // Just log the exception message. @@ -1924,6 +1928,14 @@ public abstract class BaseStatusBar extends SystemUI implements } } + protected Bundle getActivityOptions() { + // Anything launched from the notification shade should always go into the + // fullscreen stack. + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID); + return options.toBundle(); + } + protected void visibilityChanged(boolean visible) { if (mVisible != visible) { mVisible = visible; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index 8fe60a0d9d82..2b365dc743e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -245,7 +245,57 @@ public class KeyboardShortcuts { systemGroup.addItem(new KeyboardShortcutInfo( mContext.getString(R.string.keyboard_shortcut_group_system_recents), KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON)); + systemGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_system_notifications), + KeyEvent.KEYCODE_N, KeyEvent.META_META_ON)); + systemGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_system_shortcuts_helper), + KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON)); + systemGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_system_switch_input), + KeyEvent.KEYCODE_SPACE, KeyEvent.META_META_ON)); result.add(systemGroup); + + KeyboardShortcutGroup applicationGroup = new KeyboardShortcutGroup( + mContext.getString(R.string.keyboard_shortcut_group_applications), + true); + applicationGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_applications_assist), + KeyEvent.KEYCODE_UNKNOWN, KeyEvent.META_META_ON)); + applicationGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_applications_browser), + KeyEvent.KEYCODE_B, KeyEvent.META_META_ON)); + applicationGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_applications_contacts), + KeyEvent.KEYCODE_C, KeyEvent.META_META_ON)); + applicationGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_applications_email), + KeyEvent.KEYCODE_E, KeyEvent.META_META_ON)); + applicationGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_applications_im), + KeyEvent.KEYCODE_T, KeyEvent.META_META_ON)); + applicationGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_applications_music), + KeyEvent.KEYCODE_P, KeyEvent.META_META_ON)); + applicationGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_applications_youtube), + KeyEvent.KEYCODE_Y, KeyEvent.META_META_ON)); + applicationGroup.addItem(new KeyboardShortcutInfo( + mContext.getString( + R.string.keyboard_shortcut_group_applications_calendar), + KeyEvent.KEYCODE_L, KeyEvent.META_META_ON)); + result.add(applicationGroup); + showKeyboardShortcutsDialog(result); } }, deviceId); @@ -354,11 +404,15 @@ public class KeyboardShortcuts { return null; } String displayLabelString; - if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) { + if (info.getBaseCharacter() > Character.MIN_VALUE) { displayLabelString = String.valueOf(info.getBaseCharacter()); } else if (SPECIAL_CHARACTER_NAMES.get(info.getKeycode()) != null) { displayLabelString = SPECIAL_CHARACTER_NAMES.get(info.getKeycode()); } else { + // Special case for shortcuts with no base key or keycode. + if (info.getKeycode() == KeyEvent.KEYCODE_UNKNOWN) { + return shortcutKeys; + } // TODO: Have a generic map for when we don't have the device's. char displayLabel = mKeyCharacterMap == null ? 0 : mKeyCharacterMap.getDisplayLabel(info.getKeycode()); 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 4c5c84321f51..c563eb606663 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -21,7 +21,9 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManager.StackId; import android.app.ActivityManagerNative; +import android.app.ActivityOptions; import android.app.IActivityManager; import android.app.Notification; import android.app.PendingIntent; @@ -3091,8 +3093,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, null, mContext.getBasePackageName(), intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null, - UserHandle.CURRENT.getIdentifier()); + null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, + getActivityOptions(), UserHandle.CURRENT.getIdentifier()); } catch (RemoteException e) { Log.w(TAG, "Unable to start activity", e); } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java index 15ad1f1b6099..3f87611fe2d4 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipControlsView.java @@ -16,11 +16,12 @@ package com.android.systemui.tv.pip; -import android.app.Activity; import android.content.Context; import android.media.session.MediaController; import android.media.session.PlaybackState; import android.view.View; +import android.view.Gravity; +import android.view.LayoutInflater; import android.view.View.OnFocusChangeListener; import android.widget.ImageView; import android.widget.TextView; @@ -40,28 +41,29 @@ import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_UNAVAILABLE; /** * A view containing PIP controls including fullscreen, close, and media controls. */ -public class PipControlsView extends LinearLayout implements PipManager.Listener { +public class PipControlsView extends LinearLayout { /** * An interface to listen user action. */ - public interface Listener { + public abstract static interface Listener { /** * Called when an user clicks close PIP button. */ - void onClosed(); - } + public abstract void onClosed(); + }; - private final PipManager mPipManager = PipManager.getInstance(); private MediaController mMediaController; - private Listener mListener; - private View mFullButtonView; - private View mFullDescriptionView; - private View mPlayPauseView; - private ImageView mPlayPauseButtonImageView; - private TextView mPlayPauseDescriptionTextView; - private View mCloseButtonView; - private View mCloseDescriptionView; + final PipManager mPipManager = PipManager.getInstance(); + Listener mListener; + + View mFullButtonView; + View mFullDescriptionView; + View mPlayPauseView; + ImageView mPlayPauseButtonImageView; + TextView mPlayPauseDescriptionTextView; + View mCloseButtonView; + View mCloseDescriptionView; private boolean mHasFocus; private OnFocusChangeListener mOnChildFocusChangeListener; @@ -73,6 +75,13 @@ public class PipControlsView extends LinearLayout implements PipManager.Listener } }; + private PipManager.MediaListener mPipMediaListener = new PipManager.MediaListener() { + @Override + public void onMediaControllerChanged() { + updateMediaController(); + } + }; + public PipControlsView(Context context) { this(context, null, 0, 0); } @@ -87,6 +96,12 @@ public class PipControlsView extends LinearLayout implements PipManager.Listener public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + LayoutInflater inflater = (LayoutInflater) getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.tv_pip_controls, this); + + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); } @Override @@ -161,13 +176,13 @@ public class PipControlsView extends LinearLayout implements PipManager.Listener public void onAttachedToWindow() { super.onAttachedToWindow(); updateMediaController(); - mPipManager.addListener(this); + mPipManager.addMediaListener(mPipMediaListener); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); - mPipManager.removeListener(this); + mPipManager.removeMediaListener(mPipMediaListener); if (mMediaController != null) { mMediaController.unregisterCallback(mMediaControllerCallback); } @@ -230,24 +245,4 @@ public class PipControlsView extends LinearLayout implements PipManager.Listener public void setListener(Listener listener) { mListener = listener; } - - @Override - public void onPipEntered() { } - - @Override - public void onPipActivityClosed() { } - - @Override - public void onShowPipMenu() { } - - @Override - public void onMoveToFullscreen() { } - - @Override - public void onMediaControllerChanged() { - updateMediaController(); - } - - @Override - public void onPipResizeAboutToStart() { } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java index 68e0883fbc88..b5c1f5739e65 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java @@ -62,9 +62,27 @@ public class PipManager { private static final int MAX_RUNNING_TASKS_COUNT = 10; + /** + * State when there's no PIP. + */ public static final int STATE_NO_PIP = 0; + /** + * State when PIP is shown with an overlay message on top of it. + * This is used as default PIP state. + */ public static final int STATE_PIP_OVERLAY = 1; + /** + * State when PIP menu dialog is shown. + */ public static final int STATE_PIP_MENU = 2; + /** + * State when PIP is shown in Recents. + */ + public static final int STATE_PIP_RECENTS = 3; + /** + * State when PIP is shown in Recents and it's focused to allow an user to control. + */ + public static final int STATE_PIP_RECENTS_FOCUSED = 4; private static final int TASK_ID_NO_PIP = -1; private static final int INVALID_RESOURCE_TYPE = -1; @@ -90,11 +108,13 @@ public class PipManager { private int mSuspendPipResizingReason; private Context mContext; + private PipRecentsOverlayManager mPipRecentsOverlayManager; private IActivityManager mActivityManager; private MediaSessionManager mMediaSessionManager; private int mState = STATE_NO_PIP; private final Handler mHandler = new Handler(); private List<Listener> mListeners = new ArrayList<>(); + private List<MediaListener> mMediaListeners = new ArrayList<>(); private Rect mCurrentPipBounds; private Rect mPipBounds; private Rect mMenuModePipBounds; @@ -107,9 +127,6 @@ public class PipManager { private MediaController mPipMediaController; private boolean mOnboardingShown; - private boolean mIsRecentsShown; - private boolean mIsPipFocusedInRecent; - private final Runnable mResizePinnedStackRunnable = new Runnable() { @Override public void run() { @@ -178,6 +195,7 @@ public class PipManager { mOnboardingShown = Prefs.getBoolean( mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false); + mPipRecentsOverlayManager = new PipRecentsOverlayManager(context); mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); } @@ -231,7 +249,7 @@ public class PipManager { /** * Moves the PIPed activity to the fullscreen and closes PIP system UI. */ - public void movePipToFullscreen() { + void movePipToFullscreen() { mState = STATE_NO_PIP; mPipTaskId = TASK_ID_NO_PIP; for (int i = mListeners.size() - 1; i >= 0; --i) { @@ -247,8 +265,11 @@ public class PipManager { */ private void showPipOverlay() { if (DEBUG) Log.d(TAG, "showPipOverlay()"); - mState = STATE_PIP_OVERLAY; - PipOverlayActivity.showPipOverlay(mContext); + Intent intent = new Intent(mContext, PipOverlayActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchStackId(PINNED_STACK_ID); + mContext.startActivity(intent, options.toBundle()); } /** @@ -279,8 +300,10 @@ public class PipManager { * Resize the Pip to the appropriate size for the input state. * @param state In Pip state also used to determine the new size for the Pip. */ - public void resizePinnedStack(int state) { + void resizePinnedStack(int state) { if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state); + boolean wasRecentsShown = + (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED); mState = state; for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onPipResizeAboutToStart(); @@ -291,7 +314,6 @@ public class PipManager { mSuspendPipResizingReason); return; } - int animationDurationMs = -1; switch (mState) { case STATE_NO_PIP: mCurrentPipBounds = null; @@ -300,25 +322,24 @@ public class PipManager { mCurrentPipBounds = mMenuModePipBounds; break; case STATE_PIP_OVERLAY: - if (mIsRecentsShown) { - if (mCurrentPipBounds == mRecentsFocusedPipBounds - || mCurrentPipBounds == mRecentsFocusedPipBounds) { - animationDurationMs = mRecentsFocusChangedAnimationDurationMs; - } - if (mIsPipFocusedInRecent) { - mCurrentPipBounds = mRecentsFocusedPipBounds; - } else { - mCurrentPipBounds = mRecentsPipBounds; - } - } else { - mCurrentPipBounds = mPipBounds; - } + mCurrentPipBounds = mPipBounds; + break; + case STATE_PIP_RECENTS: + mCurrentPipBounds = mRecentsPipBounds; + break; + case STATE_PIP_RECENTS_FOCUSED: + mCurrentPipBounds = mRecentsFocusedPipBounds; break; default: mCurrentPipBounds = mPipBounds; break; } try { + int animationDurationMs = -1; + if (wasRecentsShown + && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) { + animationDurationMs = mRecentsFocusChangedAnimationDurationMs; + } mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true, animationDurationMs); } catch (RemoteException e) { @@ -327,67 +348,18 @@ public class PipManager { } /** - * Returns the current PIP bound for activities to sync their UI with PIP. + * Returns the default PIP bound. */ public Rect getPipBounds() { - return mCurrentPipBounds; + return mPipBounds; } /** - * Called when Recents is started. - * PIPed activity will be resized accordingly and overlay will show available buttons. + * Returns the focused PIP bound while Recents is shown. + * This is used to place PIP controls in Recents. */ - public void onRecentsStarted() { - mIsRecentsShown = true; - mIsPipFocusedInRecent = false; - if (mState == STATE_NO_PIP) { - return; - } - resizePinnedStack(STATE_PIP_OVERLAY); - } - - /** - * Called when Recents is stopped. - * PIPed activity will be resized accordingly and overlay will hide available buttons. - */ - public void onRecentsStopped() { - mIsRecentsShown = false; - mIsPipFocusedInRecent = false; - if (mState == STATE_NO_PIP) { - return; - } - resizePinnedStack(STATE_PIP_OVERLAY); - } - - /** - * Returns {@code true} if recents is shown. - */ - boolean isRecentsShown() { - return mIsRecentsShown; - } - - /** - * Called when the PIP view in {@link com.android.systemui.recents.tv.RecentsTvActivity} - * is focused. - * This only resizes pinned stack so it looks like it's in Recents. - * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}. - */ - public void onPipViewFocusChangedInRecents(boolean hasFocus) { - mIsPipFocusedInRecent = hasFocus; - if (mState != STATE_PIP_OVERLAY) { - Log.w(TAG, "There is no pinned stack to handle focus change."); - return; - } - resizePinnedStack(STATE_PIP_OVERLAY); - } - - /** - * Returns {@code true} if the PIP view in - * {@link com.android.systemui.recents.tv.RecentsTvActivity} is focused in Recents. - * This API is valid only when {@link isRecentsShown()} returns {@code true}. - */ - boolean isPipViewFocusdInRecents() { - return mIsPipFocusedInRecent; + public Rect getRecentsFocusedPipBounds() { + return mRecentsFocusedPipBounds; } /** @@ -396,6 +368,10 @@ public class PipManager { */ private void showPipMenu() { if (DEBUG) Log.d(TAG, "showPipMenu()"); + if (mPipRecentsOverlayManager.isRecentsShown()) { + if (DEBUG) Log.d(TAG, "Ignore showing PIP menu"); + return; + } mState = STATE_PIP_MENU; for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onShowPipMenu(); @@ -405,14 +381,34 @@ public class PipManager { mContext.startActivity(intent); } + /** + * Adds a {@link Listener} to PipManager. + */ public void addListener(Listener listener) { mListeners.add(listener); } + /** + * Removes a {@link Listener} from PipManager. + */ public void removeListener(Listener listener) { mListeners.remove(listener); } + /** + * Adds a {@link MediaListener} to PipManager. + */ + public void addMediaListener(MediaListener listener) { + mMediaListeners.add(listener); + } + + /** + * Removes a {@link MediaListener} from PipManager. + */ + public void removeMediaListener(MediaListener listener) { + mMediaListeners.remove(listener); + } + private void launchPipOnboardingActivityIfNeeded() { if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) { mOnboardingShown = true; @@ -485,8 +481,8 @@ public class PipManager { } if (mPipMediaController != mediaController) { mPipMediaController = mediaController; - for (int i = mListeners.size() - 1; i >= 0; i--) { - mListeners.get(i).onMediaControllerChanged(); + for (int i = mMediaListeners.size() - 1; i >= 0; i--) { + mMediaListeners.get(i).onMediaControllerChanged(); } if (mPipMediaController == null) { mHandler.postDelayed(mClosePipRunnable, @@ -530,7 +526,7 @@ public class PipManager { return PLAYBACK_STATE_UNAVAILABLE; } - TaskStackListener mTaskStackListener = new TaskStackListener() { + private TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onTaskStackChanged() { if (mState != STATE_NO_PIP) { @@ -582,10 +578,10 @@ public class PipManager { mMediaSessionManager.addOnActiveSessionsChangedListener( mActiveMediaSessionListener, null); updateMediaController(mMediaSessionManager.getActiveSessions(null)); - if (mIsRecentsShown) { + if (mPipRecentsOverlayManager.isRecentsShown()) { // If an activity becomes PIPed again after the fullscreen, the Recents is shown // behind so we need to resize the pinned stack and show the correct overlay. - resizePinnedStack(STATE_PIP_OVERLAY); + resizePinnedStack(STATE_PIP_RECENTS); } for (int i = mListeners.size() - 1; i >= 0; i--) { mListeners.get(i).onPipEntered(); @@ -604,7 +600,18 @@ public class PipManager { if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()"); switch (mState) { case STATE_PIP_OVERLAY: - showPipOverlay(); + if (!mPipRecentsOverlayManager.isRecentsShown()) { + showPipOverlay(); + break; + } else { + // This happens only if an activity is PIPed after the Recents is shown. + // See {@link PipRecentsOverlayManager.requestFocus} for more details. + resizePinnedStack(mState); + break; + } + case STATE_PIP_RECENTS: + case STATE_PIP_RECENTS_FOCUSED: + mPipRecentsOverlayManager.addPipRecentsOverlayView(); break; case STATE_PIP_MENU: showPipMenu(); @@ -621,7 +628,7 @@ public class PipManager { * Invoked when an activity is pinned and PIP manager is set corresponding information. * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned} * because there's no guarantee for the PIP manager be return relavent information - * correctly. (e.g. {@link isPipShown}, {@link getPipBounds}) + * correctly. (e.g. {@link isPipShown}). */ void onPipEntered(); /** Invoked when a PIPed activity is closed. */ @@ -632,6 +639,12 @@ public class PipManager { void onMoveToFullscreen(); /** Invoked when we are above to start resizing the Pip. */ void onPipResizeAboutToStart(); + } + + /** + * A listener interface to receive change in PIP's media controller + */ + public interface MediaListener { /** Invoked when the MediaController on PIPed activity is changed. */ void onMediaControllerChanged(); } @@ -645,4 +658,11 @@ public class PipManager { } return sPipManager; } + + /** + * Gets an instance of {@link PipRecentsOverlayManager}. + */ + public PipRecentsOverlayManager getPipRecentsOverlayManager() { + return mPipRecentsOverlayManager; + } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java index ea9275f8a918..c54e73a7cda6 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java @@ -20,12 +20,6 @@ import android.app.Activity; import android.os.Bundle; import com.android.systemui.R; -import com.android.systemui.SystemUI; -import com.android.systemui.SystemUIApplication; -import com.android.systemui.recents.Recents; - -import static android.content.pm.PackageManager.FEATURE_LEANBACK; -import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; /** * Activity to show the PIP menu to control PIP. @@ -36,7 +30,6 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { private final PipManager mPipManager = PipManager.getInstance(); private PipControlsView mPipControlsView; - private boolean mPipMovedToFullscreen; @Override protected void onCreate(Bundle bundle) { @@ -47,17 +40,10 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { mPipControlsView = (PipControlsView) findViewById(R.id.pip_controls); } - private void restorePipAndFinish() { - if (!mPipMovedToFullscreen) { - mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY); - } - finish(); - } - @Override public void onPause() { super.onPause(); - restorePipAndFinish(); + finish(); } @Override @@ -69,11 +55,6 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { } @Override - public void onBackPressed() { - restorePipAndFinish(); - } - - @Override public void onPipEntered() { } @Override @@ -86,31 +67,13 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { @Override public void onMoveToFullscreen() { - mPipMovedToFullscreen = true; finish(); } @Override - public void onMediaControllerChanged() { } - - @Override public void onPipResizeAboutToStart() { finish(); mPipManager.suspendPipResizing( PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH); } - - @Override - public void finish() { - super.finish(); - if (mPipManager.isRecentsShown() && !mPipMovedToFullscreen) { - SystemUI[] services = ((SystemUIApplication) getApplication()).getServices(); - for (int i = services.length - 1; i >= 0; i--) { - if (services[i] instanceof Recents) { - ((Recents) services[i]).showRecents(false, null); - break; - } - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java index 79daf3d51056..86ceff48408f 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java @@ -86,7 +86,4 @@ public class PipOnboardingActivity extends Activity implements PipManager.Listen @Override public void onPipResizeAboutToStart() { } - - @Override - public void onMediaControllerChanged() { } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java index 12cb4cd77824..5472ad6718dd 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java @@ -35,14 +35,6 @@ import static android.app.ActivityManager.StackId.PINNED_STACK_ID; public class PipOverlayActivity extends Activity implements PipManager.Listener { private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 4000; - /** - * The single instance of PipOverlayActivity to prevent it from restarting. - * Note that {@link PipManager} moves the PIPed activity to fullscreen if the activity is - * restarted. It's because the activity may be started by the Launcher or an intent again, - * but we don't want do so for the PipOverlayActivity. - */ - private static PipOverlayActivity sPipOverlayActivity; - private final PipManager mPipManager = PipManager.getInstance(); private final Handler mHandler = new Handler(); private View mGuideOverlayView; @@ -54,47 +46,17 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener } }; - /** - * Launches the PIP overlay. This should be only called on the main thread. - */ - public static void showPipOverlay(Context context) { - if (sPipOverlayActivity == null) { - Intent intent = new Intent(context, PipOverlayActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchStackId(PINNED_STACK_ID); - context.startActivity(intent, options.toBundle()); - } - } - @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.tv_pip_overlay); mGuideOverlayView = findViewById(R.id.guide_overlay); - mGuideButtonsView = findViewById(R.id.guide_buttons); - mGuideButtonPlayPauseImageView = (ImageView) findViewById(R.id.guide_button_play_pause); mPipManager.addListener(this); - - sPipOverlayActivity = this; } @Override protected void onResume() { super.onResume(); - // TODO: Implement animation for this - if (mPipManager.isRecentsShown()) { - mGuideOverlayView.setVisibility(View.GONE); - if (mPipManager.isPipViewFocusdInRecents()) { - mGuideButtonsView.setVisibility(View.GONE); - } else { - mGuideButtonsView.setVisibility(View.VISIBLE); - updateGuideButtonsView(); - } - } else { - mGuideOverlayView.setVisibility(View.VISIBLE); - mGuideButtonsView.setVisibility(View.GONE); - } mHandler.removeCallbacks(mHideGuideOverlayRunnable); mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS); } @@ -109,7 +71,6 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener @Override protected void onDestroy() { super.onDestroy(); - sPipOverlayActivity = null; mHandler.removeCallbacksAndMessages(null); mPipManager.removeListener(this); mPipManager.resumePipResizing( @@ -140,32 +101,4 @@ public class PipOverlayActivity extends Activity implements PipManager.Listener mPipManager.suspendPipResizing( PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH); } - - @Override - public void onMediaControllerChanged() { - updateGuideButtonsView(); - } - - @Override - public void finish() { - sPipOverlayActivity = null; - super.finish(); - } - - private void updateGuideButtonsView() { - switch (mPipManager.getPlaybackState()) { - case PipManager.PLAYBACK_STATE_PLAYING: - mGuideButtonPlayPauseImageView.setVisibility(View.VISIBLE); - mGuideButtonPlayPauseImageView.setImageResource(R.drawable.ic_pause_white_24dp); - break; - case PipManager.PLAYBACK_STATE_PAUSED: - mGuideButtonPlayPauseImageView.setVisibility(View.VISIBLE); - mGuideButtonPlayPauseImageView.setImageResource( - R.drawable.ic_play_arrow_white_24dp); - break; - case PipManager.PLAYBACK_STATE_UNAVAILABLE: - mGuideButtonPlayPauseImageView.setVisibility(View.GONE); - break; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java new file mode 100644 index 000000000000..8b8c1058bb99 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsControlsView.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.tv.pip; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.AnimatorSet; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnFocusChangeListener; + +import com.android.systemui.R; + +import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PLAYING; +import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_PAUSED; +import static com.android.systemui.tv.pip.PipManager.PLAYBACK_STATE_UNAVAILABLE; + +/** + * An extended version of {@link PipControlsView} that supports animation in Recents. + */ +public class PipRecentsControlsView extends PipControlsView { + /** + * An interface to listen user action. + */ + public interface Listener extends PipControlsView.Listener { + /** + * Called when an user presses BACK key and up. + */ + abstract void onBackPressed(); + } + + private AnimatorSet mFocusGainAnimatorSet; + private AnimatorSet mFocusLoseAnimatorSet; + + public PipRecentsControlsView(Context context) { + this(context, null, 0, 0); + } + + public PipRecentsControlsView(Context context, AttributeSet attrs) { + this(context, attrs, 0, 0); + } + + public PipRecentsControlsView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PipRecentsControlsView( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + + int buttonsFocusGainAnim = R.anim.tv_pip_controls_buttons_in_recents_focus_gain_animation; + int textFocusGainAnim = R.anim.tv_pip_controls_text_in_recents_focus_gain_animation; + mFocusGainAnimatorSet = new AnimatorSet(); + mFocusGainAnimatorSet.playTogether( + loadAnimator(this, R.anim.tv_pip_controls_in_recents_focus_gain_animation), + loadAnimator(mFullButtonView,buttonsFocusGainAnim), + loadAnimator(mPlayPauseButtonImageView, buttonsFocusGainAnim), + loadAnimator(mCloseButtonView, buttonsFocusGainAnim), + loadAnimator(mFullDescriptionView, textFocusGainAnim), + loadAnimator(mPlayPauseDescriptionTextView, textFocusGainAnim), + loadAnimator(mCloseDescriptionView, textFocusGainAnim)); + + int buttonsFocusLoseAnim = R.anim.tv_pip_controls_buttons_in_recents_focus_lose_animation; + int textFocusLoseAnim = R.anim.tv_pip_controls_text_in_recents_focus_lose_animation; + mFocusLoseAnimatorSet = new AnimatorSet(); + mFocusLoseAnimatorSet.playTogether( + loadAnimator(this, R.anim.tv_pip_controls_in_recents_focus_lose_animation), + loadAnimator(mFullButtonView, buttonsFocusLoseAnim), + loadAnimator(mPlayPauseButtonImageView, buttonsFocusLoseAnim), + loadAnimator(mCloseButtonView, buttonsFocusLoseAnim), + loadAnimator(mFullDescriptionView, textFocusLoseAnim), + loadAnimator(mPlayPauseDescriptionTextView, textFocusLoseAnim), + loadAnimator(mCloseDescriptionView, textFocusLoseAnim)); + + Rect pipBounds = mPipManager.getRecentsFocusedPipBounds(); + int pipControlsMarginTop = getContext().getResources().getDimensionPixelSize( + R.dimen.recents_tv_pip_controls_margin_top); + setPadding(0, pipBounds.bottom + pipControlsMarginTop, 0, 0); + } + + private Animator loadAnimator(View view, int animatorResId) { + Animator animator = AnimatorInflater.loadAnimator(getContext(), animatorResId); + animator.setTarget(view); + return animator; + } + + /** + * Starts focus gaining animation. + */ + public void startFocusGainAnimation() { + if (mFocusLoseAnimatorSet.isStarted()) { + mFocusLoseAnimatorSet.cancel(); + } + mFocusGainAnimatorSet.start(); + } + + /** + * Starts focus losing animation. + */ + public void startFocusLoseAnimation() { + if (mFocusGainAnimatorSet.isStarted()) { + mFocusGainAnimatorSet.cancel(); + } + mFocusLoseAnimatorSet.start(); + } + + /** + * Resets the view to the initial state. (i.e. end of the focus gain) + */ + public void reset() { + if (mFocusGainAnimatorSet.isStarted()) { + mFocusGainAnimatorSet.cancel(); + } + if (mFocusLoseAnimatorSet.isStarted()) { + mFocusLoseAnimatorSet.cancel(); + } + + // Reset to initial state (i.e. end of focused) + requestFocus(); + setTranslationY(0); + setScaleXY(mFullButtonView, 1); + setScaleXY(mPlayPauseButtonImageView, 1); + setScaleXY(mCloseButtonView, 1); + mFullDescriptionView.setAlpha(1); + mPlayPauseDescriptionTextView.setAlpha(1); + mCloseDescriptionView.setAlpha(1); + } + + private void setScaleXY(View view, float scale) { + view.setScaleX(scale); + view.setScaleY(scale); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (!event.isCanceled() + && event.getKeyCode() == KeyEvent.KEYCODE_BACK + && event.getAction() == KeyEvent.ACTION_UP) { + if (mListener != null) { + ((PipRecentsControlsView.Listener) mListener).onBackPressed(); + } + return true; + } + return super.dispatchKeyEvent(event); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java new file mode 100644 index 000000000000..b90b7279a5b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipRecentsOverlayManager.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.tv.pip; + +import android.animation.AnimatorInflater; +import android.animation.AnimatorSet; +import android.content.Context; +import android.graphics.PixelFormat; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager.LayoutParams; +import android.view.WindowManager; + +import com.android.systemui.R; + +import static com.android.systemui.tv.pip.PipManager.STATE_PIP_OVERLAY; +import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS; +import static com.android.systemui.tv.pip.PipManager.STATE_PIP_RECENTS_FOCUSED; + +public class PipRecentsOverlayManager { + private static final String TAG = "PipRecentsOverlayManager"; + + public interface Callback { + void onClosed(); + void onBackPressed(); + void onRecentsFocused(); + } + + private final PipManager mPipManager = PipManager.getInstance(); + private final WindowManager mWindowManager; + private final View mOverlayView; + private final PipRecentsControlsView mPipControlsView; + private final View mRecentsView; + + private final LayoutParams mPipRecentsControlsViewLayoutParams; + private final LayoutParams mPipRecentsControlsViewFocusedLayoutParams; + + private boolean mIsPipRecentsOverlayShown; + private boolean mIsRecentsShown; + private boolean mIsPipFocusedInRecent; + private Callback mCallback; + private PipRecentsControlsView.Listener mPipControlsViewListener = + new PipRecentsControlsView.Listener() { + @Override + public void onClosed() { + if (mCallback != null) { + mCallback.onClosed(); + } + } + + @Override + public void onBackPressed() { + if (mCallback != null) { + mCallback.onBackPressed(); + } + } + }; + + PipRecentsOverlayManager(Context context) { + mWindowManager = (WindowManager) context.getSystemService(WindowManager.class); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mOverlayView = inflater.inflate(R.layout.tv_pip_recents_overlay, null); + mPipControlsView = (PipRecentsControlsView) mOverlayView.findViewById(R.id.pip_controls); + mRecentsView = mOverlayView.findViewById(R.id.recents); + mRecentsView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + clearFocus(); + } + } + }); + + mPipRecentsControlsViewLayoutParams = new WindowManager.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + LayoutParams.TYPE_SYSTEM_DIALOG, + LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE, + PixelFormat.TRANSLUCENT); + mPipRecentsControlsViewFocusedLayoutParams = new WindowManager.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + LayoutParams.TYPE_SYSTEM_DIALOG, + 0, + PixelFormat.TRANSLUCENT); + } + + /** + * Add Recents overlay view. + * This is expected to be called after the PIP animation is over. + */ + void addPipRecentsOverlayView() { + if (mIsPipRecentsOverlayShown) { + return; + } + mIsPipRecentsOverlayShown = true; + mIsPipFocusedInRecent = true; + mPipControlsView.reset(); + mWindowManager.addView(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams); + } + + /** + * Remove Recents overlay view. + * This should be called when Recents or PIP is closed. + */ + public void removePipRecentsOverlayView() { + if (!mIsPipRecentsOverlayShown) { + return; + } + mWindowManager.removeView(mOverlayView); + mIsPipRecentsOverlayShown = false; + } + + /** + * Request focus to the PIP Recents overlay. + * Called when the PIP view in {@link com.android.systemui.recents.tv.RecentsTvActivity} + * is focused. + * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}. + * @param hasRecentsFocusable {@code true} if Recents can have focus. (i.e. Has a recent task) + */ + public void requestFocus(boolean hasRecentsFocusable) { + if (!mIsRecentsShown || mIsPipFocusedInRecent) { + return; + } + mIsPipFocusedInRecent = true; + mPipManager.resizePinnedStack(STATE_PIP_RECENTS_FOCUSED); + + mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams); + mPipControlsView.requestFocus(); + mPipControlsView.startFocusGainAnimation(); + mRecentsView.setVisibility(hasRecentsFocusable ? View.VISIBLE : View.GONE); + } + + /** + * Request focus to the PIP Recents overlay. + * Called when the PIP view in {@link com.android.systemui.recents.tv.RecentsTvActivity} + * is focused. + * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}. + */ + private void clearFocus() { + if (!mIsRecentsShown || !mIsPipFocusedInRecent) { + return; + } + mIsPipFocusedInRecent = false; + mPipManager.resizePinnedStack(STATE_PIP_RECENTS); + mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewLayoutParams); + mPipControlsView.startFocusLoseAnimation(); + if (mCallback != null) { + mCallback.onRecentsFocused(); + } + } + + public void setCallback(Callback listener) { + mCallback = listener; + mPipControlsView.setListener(mCallback != null ? mPipControlsViewListener : null); + } + + /** + * Called when Recents is resumed. + * PIPed activity will be resized accordingly and overlay will show available buttons. + */ + public void onRecentsResumed() { + if (!mPipManager.isPipShown()) { + return; + } + mIsRecentsShown = true; + mIsPipFocusedInRecent = true; + mPipManager.resizePinnedStack(STATE_PIP_RECENTS_FOCUSED); + // Overlay view will be added after the resize animation ends, if any. + } + + /** + * Called when Recents is paused. + * PIPed activity will be resized accordingly and overlay will hide available buttons. + */ + public void onRecentsPaused() { + mIsRecentsShown = false; + mIsPipFocusedInRecent = false; + removePipRecentsOverlayView(); + + if (mPipManager.isPipShown()) { + mPipManager.resizePinnedStack(STATE_PIP_OVERLAY); + } + } + + /** + * Returns {@code true} if recents is shown. + */ + boolean isRecentsShown() { + return mIsRecentsShown; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java index f4328089bf0f..c6e4356aa420 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java +++ b/packages/SystemUI/src/com/android/systemui/volume/SegmentedButtons.java @@ -35,11 +35,11 @@ public class SegmentedButtons extends LinearLayout { private static final Typeface MEDIUM = Typeface.create("sans-serif-medium", Typeface.NORMAL); private final Context mContext; - private final LayoutInflater mInflater; + protected final LayoutInflater mInflater; private final SpTexts mSpTexts; private Callback mCallback; - private Object mSelectedValue; + protected Object mSelectedValue; public SegmentedButtons(Context context, AttributeSet attrs) { super(context, attrs); @@ -65,13 +65,21 @@ public class SegmentedButtons extends LinearLayout { final Object tag = c.getTag(); final boolean selected = Objects.equals(mSelectedValue, tag); c.setSelected(selected); - c.setTypeface(selected ? MEDIUM : REGULAR); + setSelectedStyle(c, selected); } fireOnSelected(fromClick); } + protected void setSelectedStyle(TextView textView, boolean selected) { + textView.setTypeface(selected ? MEDIUM : REGULAR); + } + + public Button inflateButton() { + return (Button) mInflater.inflate(R.layout.segmented_button, this, false); + } + public void addButton(int labelResId, int contentDescriptionResId, Object value) { - final Button b = (Button) mInflater.inflate(R.layout.segmented_button, this, false); + final Button b = inflateButton(); b.setTag(LABEL_RES_KEY, labelResId); b.setText(labelResId); b.setContentDescription(getResources().getString(contentDescriptionResId)); diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 799a763845d2..0a814ab579d3 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -92,6 +92,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private static final int MESSAGE_BLUETOOTH_STATE_CHANGE = 60; private static final int MESSAGE_TIMEOUT_BIND = 100; private static final int MESSAGE_TIMEOUT_UNBIND = 101; + private static final int MESSAGE_GET_NAME_AND_ADDRESS = 200; private static final int MESSAGE_USER_SWITCHED = 300; private static final int MESSAGE_USER_UNLOCKED = 301; private static final int MESSAGE_ADD_PROXY_DELAYED = 400; @@ -599,8 +600,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { sendEnableMsg(true); } return true; - } + public boolean enable() { if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { @@ -763,9 +764,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { sendEnableMsg(mQuietEnableExternal); } else if (!isNameAndAddressSet()) { if (DBG) Slog.d(TAG, "Getting adapter name and address"); - enable(); - waitForOnOff(true, false); - disable(true); + Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); + mHandler.sendMessage(getMsg); } } @@ -1076,6 +1076,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private BluetoothServiceConnection mConnection = new BluetoothServiceConnection(); private class BluetoothHandler extends Handler { + boolean mGetNameAddressOnly = false; + public BluetoothHandler(Looper looper) { super(looper); } @@ -1084,6 +1086,37 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public void handleMessage(Message msg) { if (DBG) Slog.d (TAG, "Message: " + msg.what); switch (msg.what) { + case MESSAGE_GET_NAME_AND_ADDRESS: + if (DBG) Slog.d(TAG, "MESSAGE_GET_NAME_AND_ADDRESS"); + synchronized(mConnection) { + if ((mBluetooth == null) && (!mBinding)) { + if (DBG) Slog.d(TAG, "Binding to service to get name and address"); + mGetNameAddressOnly = true; + Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND); + mHandler.sendMessageDelayed(timeoutMsg, TIMEOUT_BIND_MS); + Intent i = new Intent(IBluetooth.class.getName()); + if (!doBind(i, mConnection, + Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, + UserHandle.CURRENT)) { + mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); + } else { + mBinding = true; + } + } else if (mBluetooth != null) { + try { + storeNameAndAddress(mBluetooth.getName(), + mBluetooth.getAddress()); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to grab names", re); + } + if (mGetNameAddressOnly && !mEnable) { + unbindAndFinish(); + } + mGetNameAddressOnly = false; + } + } + break; + case MESSAGE_ENABLE: if (DBG) { Slog.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth); @@ -1177,6 +1210,12 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mBluetoothBinder = service; mBluetooth = IBluetooth.Stub.asInterface(service); + if (!isNameAndAddressSet()) { + Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); + mHandler.sendMessage(getMsg); + if (mGetNameAddressOnly) return; + } + try { boolean enableHciSnoopLog = (Settings.Secure.getInt(mContentResolver, Settings.Secure.BLUETOOTH_HCI_LOG, 0) == 1); @@ -1187,15 +1226,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { Slog.e(TAG,"Unable to call configHciSnoopLog", e); } - if (!isNameAndAddressSet()) { - try { - storeNameAndAddress(mBluetooth.getName(), - mBluetooth.getAddress()); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to grab names", re); - } - } - //Register callback object try { mBluetooth.registerCallback(mBluetoothCallback); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index f667fb34b370..8a0a62a033ed 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -2518,21 +2518,31 @@ public class AccountManagerService userId); return; } - final int pid = Binder.getCallingPid(); final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn; options.putInt(AccountManager.KEY_CALLER_UID, uid); options.putInt(AccountManager.KEY_CALLER_PID, pid); + // Check to see if the Password should be included to the caller. + String callerPkg = optionsIn.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME); + boolean isPasswordForwardingAllowed = isPermitted( + callerPkg, uid, Manifest.permission.GET_PASSWORD_PRIVILEGED); + int usrId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { UserAccounts accounts = getUserAccounts(usrId); logRecordWithUid(accounts, DebugDbHelper.ACTION_CALLED_START_ACCOUNT_ADD, TABLE_ACCOUNTS, uid); - new StartAccountSession(accounts, response, accountType, expectActivityLaunch, - null /* accountName */, false /* authDetailsRequired */, - true /* updateLastAuthenticationTime */) { + new StartAccountSession( + accounts, + response, + accountType, + expectActivityLaunch, + null /* accountName */, + false /* authDetailsRequired */, + true /* updateLastAuthenticationTime */, + isPasswordForwardingAllowed) { @Override public void run() throws RemoteException { mAuthenticator.startAddAccountSession(this, mAccountType, authTokenType, @@ -2555,12 +2565,21 @@ public class AccountManagerService /** Session that will encrypt the KEY_ACCOUNT_SESSION_BUNDLE in result. */ private abstract class StartAccountSession extends Session { - public StartAccountSession(UserAccounts accounts, IAccountManagerResponse response, - String accountType, boolean expectActivityLaunch, String accountName, - boolean authDetailsRequired, boolean updateLastAuthenticationTime) { + private final boolean mIsPasswordForwardingAllowed; + + public StartAccountSession( + UserAccounts accounts, + IAccountManagerResponse response, + String accountType, + boolean expectActivityLaunch, + String accountName, + boolean authDetailsRequired, + boolean updateLastAuthenticationTime, + boolean isPasswordForwardingAllowed) { super(accounts, response, accountType, expectActivityLaunch, true /* stripAuthTokenFromResult */, accountName, authDetailsRequired, updateLastAuthenticationTime); + mIsPasswordForwardingAllowed = isPasswordForwardingAllowed; } @Override @@ -2573,6 +2592,10 @@ public class AccountManagerService checkKeyIntent( Binder.getCallingUid(), intent); + // Omit passwords if the caller isn't permitted to see them. + if (!mIsPasswordForwardingAllowed) { + result.remove(AccountManager.KEY_PASSWORD); + } } IAccountManagerResponse response; if (mExpectActivityLaunch && result != null @@ -2919,6 +2942,12 @@ public class AccountManagerService } int userId = UserHandle.getCallingUserId(); + + // Check to see if the Password should be included to the caller. + String callerPkg = loginOptions.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME); + boolean isPasswordForwardingAllowed = isPermitted( + callerPkg, uid, Manifest.permission.GET_PASSWORD_PRIVILEGED); + long identityToken = clearCallingIdentity(); try { UserAccounts accounts = getUserAccounts(userId); @@ -2929,7 +2958,8 @@ public class AccountManagerService expectActivityLaunch, account.name, false /* authDetailsRequired */, - true /* updateLastCredentialTime */) { + true /* updateLastCredentialTime */, + isPasswordForwardingAllowed) { @Override public void run() throws RemoteException { mAuthenticator.startUpdateCredentialsSession(this, account, authTokenType, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e82c6c7a22a4..ef0577327596 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9827,7 +9827,8 @@ public final class ActivityManagerService extends ActivityManagerNative public void updateLockTaskPackages(int userId, String[] packages) { final int callingUid = Binder.getCallingUid(); if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { - throw new SecurityException("updateLockTaskPackage called from non-system process"); + enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES, + "updateLockTaskPackages()"); } synchronized (this) { if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":" + @@ -18044,6 +18045,9 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityRecord starting, boolean initLocale, boolean persistent, int userId) { int changes = 0; + if (mWindowManager != null) { + mWindowManager.deferSurfaceLayout(); + } if (values != null) { Configuration newConfig = new Configuration(mConfiguration); changes = newConfig.updateFrom(values); @@ -18144,6 +18148,20 @@ public final class ActivityManagerService extends ActivityManagerNative null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); } } + // Update the configuration with WM first and check if any of the stacks need to be + // resized due to the configuration change. If so, resize the stacks now and do any + // relaunches if necessary. This way we don't need to relaunch again below in + // ensureActivityConfigurationLocked(). + if (mWindowManager != null) { + final int[] resizedStacks = mWindowManager.setNewConfiguration(mConfiguration); + if (resizedStacks != null) { + for (int stackId : resizedStacks) { + final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId); + mStackSupervisor.resizeStackLocked( + stackId, newBounds, null, null, false, false); + } + } + } } boolean kept = true; @@ -18165,11 +18183,9 @@ public final class ActivityManagerService extends ActivityManagerNative !PRESERVE_WINDOWS); } } - - if (values != null && mWindowManager != null) { - mWindowManager.setNewConfiguration(mConfiguration); + if (mWindowManager != null) { + mWindowManager.continueSurfaceLayout(); } - return kept; } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index d570be94a8a4..9be6b43f21c6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -23,8 +23,6 @@ import android.os.ShellCommand; import android.os.UserHandle; import android.util.DebugUtils; -import com.android.internal.util.ArrayUtils; - import java.io.PrintWriter; class ActivityManagerShellCommand extends ShellCommand { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 0d70e9901f68..811b48fc4fe5 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2738,12 +2738,21 @@ public final class ActivityStackSupervisor implements DisplayListener { // 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.setLastThumbnailLocked(task.stack.screenshotActivitiesLocked(r)); + final ActivityStack stack = task.stack; + + r.mLaunchTaskBehind = false; + task.setLastThumbnailLocked(stack.screenshotActivitiesLocked(r)); mRecentTasks.addLocked(task); mService.notifyTaskStackChangedLocked(); mWindowManager.setAppVisibility(r.appToken, false); + + // When launching tasks behind, update the last active time of the top task after the new + // task has been shown briefly + final ActivityRecord top = stack.topActivity(); + if (top != null) { + top.task.touchActiveTime(); + } } void scheduleLaunchTaskBehindComplete(IBinder token) { diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index b157070b1184..e32d1d1fa19e 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -285,6 +285,7 @@ final class TaskRecord { mCallingPackage = info.packageName; setIntent(_intent, info); setMinDimensions(info); + touchActiveTime(); } TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent, @@ -315,6 +316,7 @@ final class TaskRecord { taskType = APPLICATION_ACTIVITY_TYPE; mTaskToReturnTo = HOME_ACTIVITY_TYPE; lastTaskDescription = _taskDescription; + touchActiveTime(); } private TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java index 9cfb590c9234..d339f69d3e28 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java @@ -24,7 +24,6 @@ import static com.android.server.net.NetworkPolicyManagerService.TAG; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -35,7 +34,6 @@ import android.net.NetworkPolicy; import android.net.NetworkTemplate; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; -import android.os.Binder; import android.os.RemoteException; import android.os.ShellCommand; import android.util.Log; @@ -88,12 +86,10 @@ class NetworkPolicyManagerShellCommand extends ShellCommand { pw.println(" Adds a UID to the whitelist for restrict background usage."); pw.println(" add restrict-background-blacklist UID"); pw.println(" Adds a UID to the blacklist for restrict background usage."); - pw.println(" get metered-network ID"); - pw.println(" Checks whether the given non-mobile network is metered or not."); pw.println(" get restrict-background"); pw.println(" Gets the global restrict background usage status."); - pw.println(" list metered-networks [BOOLEAN]"); - pw.println(" Lists all non-mobile networks and whether they are metered or not."); + pw.println(" list wifi-networks [BOOLEAN]"); + pw.println(" Lists all saved wifi networks and whether they are metered or not."); pw.println(" If a boolean argument is passed, filters just the metered (or unmetered)"); pw.println(" networks."); pw.println(" list restrict-background-whitelist"); @@ -105,7 +101,7 @@ class NetworkPolicyManagerShellCommand extends ShellCommand { pw.println(" remove restrict-background-blacklist UID"); pw.println(" Removes a UID from the blacklist for restrict background usage."); pw.println(" set metered-network ID BOOLEAN"); - pw.println(" Toggles whether the given non-mobile network is metered."); + pw.println(" Toggles whether the given wi-fi network is metered."); pw.println(" set restrict-background BOOLEAN"); pw.println(" Sets the global restrict background usage status."); } @@ -118,8 +114,6 @@ class NetworkPolicyManagerShellCommand extends ShellCommand { return -1; } switch(type) { - case "metered-network": - return getMeteredWifiNetwork(); case "restrict-background": return getRestrictBackground(); } @@ -152,8 +146,8 @@ class NetworkPolicyManagerShellCommand extends ShellCommand { return -1; } switch(type) { - case "metered-networks": - return listMeteredWifiNetworks(); + case "wifi-networks": + return listWifiNetworks(); case "restrict-background-whitelist": return listRestrictBackgroundWhitelist(); case "restrict-background-blacklist": @@ -284,7 +278,7 @@ class NetworkPolicyManagerShellCommand extends ShellCommand { return 0; } - private int listMeteredWifiNetworks() throws RemoteException { + private int listWifiNetworks() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); final String arg = getNextArg(); final Boolean filter = arg == null ? null : Boolean.valueOf(arg); @@ -299,23 +293,6 @@ class NetworkPolicyManagerShellCommand extends ShellCommand { return 0; } - private int getMeteredWifiNetwork() throws RemoteException { - final PrintWriter pw = getOutPrintWriter(); - final String id = getNextArg(); - if (id == null) { - pw.println("Error: didn't specify ID"); - return -1; - } - final List<NetworkPolicy> policies = getWifiPolicies(); - for (NetworkPolicy policy: policies) { - if (id.equals(getNetworkId(policy))) { - pw.println(policy.metered); - return 0; - } - } - return 0; - } - private int setMeteredWifiNetwork() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); final String id = getNextArg(); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 022b10fb27aa..124d7f1b9c97 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -120,6 +120,7 @@ import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -226,7 +227,7 @@ public class NotificationManagerService extends SystemService { private VrManagerInternal mVrManagerInternal; final IBinder mForegroundToken = new Binder(); - private WorkerHandler mHandler; + private Handler mHandler; private final HandlerThread mRankingThread = new HandlerThread("ranker", Process.THREAD_PRIORITY_BACKGROUND); @@ -572,33 +573,9 @@ public class NotificationManagerService extends SystemService { public void clearEffects() { synchronized (mNotificationList) { if (DBG) Slog.d(TAG, "clearEffects"); - - // sound - mSoundNotificationKey = null; - - long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); - if (player != null) { - player.stopAsync(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - - // vibrate - mVibrateNotificationKey = null; - identity = Binder.clearCallingIdentity(); - try { - mVibrator.cancel(); - } finally { - Binder.restoreCallingIdentity(identity); - } - - // light - mLights.clear(); - updateLightsLocked(); + clearSoundLocked(); + clearVibrateLocked(); + clearLightsLocked(); } } @@ -658,6 +635,36 @@ public class NotificationManagerService extends SystemService { } }; + private void clearSoundLocked() { + mSoundNotificationKey = null; + long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); + if (player != null) { + player.stopAsync(); + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void clearVibrateLocked() { + mVibrateNotificationKey = null; + long identity = Binder.clearCallingIdentity(); + try { + mVibrator.cancel(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void clearLightsLocked() { + // light + mLights.clear(); + updateLightsLocked(); + } + private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -863,6 +870,26 @@ public class NotificationManagerService extends SystemService { super(context); } + @VisibleForTesting + void setAudioManager(AudioManager audioMananger) { + mAudioManager = audioMananger; + } + + @VisibleForTesting + void setVibrator(Vibrator vibrator) { + mVibrator = vibrator; + } + + @VisibleForTesting + void setSystemReady(boolean systemReady) { + mSystemReady = systemReady; + } + + @VisibleForTesting + void setHandler(Handler handler) { + mHandler = handler; + } + @Override public void onStart() { Resources resources = getContext().getResources(); @@ -2492,12 +2519,14 @@ public class NotificationManagerService extends SystemService { return false; } - private void buzzBeepBlinkLocked(NotificationRecord record) { + @VisibleForTesting + void buzzBeepBlinkLocked(NotificationRecord record) { boolean buzz = false; boolean beep = false; boolean blink = false; final Notification notification = record.sbn.getNotification(); + final String key = record.getKey(); // Should this notification make noise, vibe, or use the LED? final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT; @@ -2521,9 +2550,15 @@ public class NotificationManagerService extends SystemService { if (disableEffects != null) { ZenLog.traceDisableEffects(record, disableEffects); } + + // Remember if this notification already owns the notification channels. + boolean wasBeep = key != null && key.equals(mSoundNotificationKey); + boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey); + + // These are set inside the conditional if the notification is allowed to make noise. + boolean hasValidVibrate = false; + boolean hasValidSound = false; if (disableEffects == null - && (!(record.isUpdate - && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) && (record.getUserId() == UserHandle.USER_ALL || record.getUserId() == currentUser || mUserProfiles.isCurrentProfile(record.getUserId())) @@ -2532,10 +2567,6 @@ public class NotificationManagerService extends SystemService { && mAudioManager != null) { if (DBG) Slog.v(TAG, "Interrupting!"); - sendAccessibilityEvent(notification, record.sbn.getPackageName()); - - // sound - // should we use the default notification sound? (indicated either by // DEFAULT_SOUND or because notification.sound is pointing at // Settings.System.NOTIFICATION_SOUND) @@ -2545,8 +2576,6 @@ public class NotificationManagerService extends SystemService { .equals(notification.sound); Uri soundUri = null; - boolean hasValidSound = false; - if (useDefaultSound) { soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; @@ -2559,88 +2588,105 @@ public class NotificationManagerService extends SystemService { hasValidSound = (soundUri != null); } - if (hasValidSound) { - boolean looping = - (notification.flags & Notification.FLAG_INSISTENT) != 0; - AudioAttributes audioAttributes = audioAttributesForNotification(notification); - mSoundNotificationKey = record.getKey(); - // do not play notifications if stream volume is 0 (typically because - // ringer mode is silent) or if there is a user of exclusive audio focus - if ((mAudioManager.getStreamVolume( - AudioAttributes.toLegacyStreamType(audioAttributes)) != 0) - && !mAudioManager.isAudioFocusExclusive()) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = - mAudioManager.getRingtonePlayer(); - if (player != null) { - if (DBG) Slog.v(TAG, "Playing sound " + soundUri - + " with attributes " + audioAttributes); - player.playAsync(soundUri, record.sbn.getUser(), looping, - audioAttributes); - beep = true; - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - // vibrate // Does the notification want to specify its own vibration? final boolean hasCustomVibrate = notification.vibrate != null; // new in 4.2: if there was supposed to be a sound and we're in vibrate // mode, and no other vibration is specified, we fall back to vibration final boolean convertSoundToVibration = - !hasCustomVibrate - && hasValidSound - && (mAudioManager.getRingerModeInternal() - == AudioManager.RINGER_MODE_VIBRATE); + !hasCustomVibrate + && hasValidSound + && (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE); // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; - if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) - && !(mAudioManager.getRingerModeInternal() - == AudioManager.RINGER_MODE_SILENT)) { - mVibrateNotificationKey = record.getKey(); + hasValidVibrate = useDefaultVibrate || convertSoundToVibration || + hasCustomVibrate; + + // We can alert, and we're allowed to alert, but if the developer asked us to only do + // it once, and we already have, then don't. + if (!(record.isUpdate + && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)) { + + sendAccessibilityEvent(notification, record.sbn.getPackageName()); + + if (hasValidSound) { + boolean looping = + (notification.flags & Notification.FLAG_INSISTENT) != 0; + AudioAttributes audioAttributes = audioAttributesForNotification(notification); + mSoundNotificationKey = key; + // do not play notifications if stream volume is 0 (typically because + // ringer mode is silent) or if there is a user of exclusive audio focus + if ((mAudioManager.getStreamVolume( + AudioAttributes.toLegacyStreamType(audioAttributes)) != 0) + && !mAudioManager.isAudioFocusExclusive()) { + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = + mAudioManager.getRingtonePlayer(); + if (player != null) { + if (DBG) Slog.v(TAG, "Playing sound " + soundUri + + " with attributes " + audioAttributes); + player.playAsync(soundUri, record.sbn.getUser(), looping, + audioAttributes); + beep = true; + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } - if (useDefaultVibrate || convertSoundToVibration) { - // Escalate privileges so we can use the vibrator even if the - // notifying app does not have the VIBRATE permission. - long identity = Binder.clearCallingIdentity(); - try { + if (hasValidVibrate && !(mAudioManager.getRingerModeInternal() + == AudioManager.RINGER_MODE_SILENT)) { + mVibrateNotificationKey = key; + + if (useDefaultVibrate || convertSoundToVibration) { + // Escalate privileges so we can use the vibrator even if the + // notifying app does not have the VIBRATE permission. + long identity = Binder.clearCallingIdentity(); + try { + mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), + useDefaultVibrate ? mDefaultVibrationPattern + : mFallbackVibrationPattern, + ((notification.flags & Notification.FLAG_INSISTENT) != 0) + ? 0: -1, audioAttributesForNotification(notification)); + buzz = true; + } finally { + Binder.restoreCallingIdentity(identity); + } + } else if (notification.vibrate.length > 1) { + // If you want your own vibration pattern, you need the VIBRATE + // permission mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), - useDefaultVibrate ? mDefaultVibrationPattern - : mFallbackVibrationPattern, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1, audioAttributesForNotification(notification)); + notification.vibrate, + ((notification.flags & Notification.FLAG_INSISTENT) != 0) + ? 0: -1, audioAttributesForNotification(notification)); buzz = true; - } finally { - Binder.restoreCallingIdentity(identity); } - } else if (notification.vibrate.length > 1) { - // If you want your own vibration pattern, you need the VIBRATE - // permission - mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), - notification.vibrate, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) - ? 0: -1, audioAttributesForNotification(notification)); - buzz = true; } } + + } + // If a notification is updated to remove the actively playing sound or vibrate, + // cancel that feedback now + if (wasBeep && !hasValidSound) { + clearSoundLocked(); + } + if (wasBuzz && !hasValidVibrate) { + clearVibrateLocked(); } // light // release the light - boolean wasShowLights = mLights.remove(record.getKey()); + boolean wasShowLights = mLights.remove(key); if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold && ((record.getSuppressedVisualEffects() & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) { - mLights.add(record.getKey()); + mLights.add(key); updateLightsLocked(); if (mUseAttentionLight) { mAttentionLight.pulse(); @@ -2654,7 +2700,7 @@ public class NotificationManagerService extends SystemService { & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) { if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on"); } else { - EventLogTags.writeNotificationAlert(record.getKey(), + EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); mHandler.post(mBuzzBeepBlinked); } diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index 13a96ae50cbf..e496132a7451 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -247,10 +247,8 @@ final class DefaultPermissionGrantPolicy { } // SetupWizard - Intent setupIntent = new Intent(Intent.ACTION_MAIN); - setupIntent.addCategory(Intent.CATEGORY_SETUP_WIZARD); - PackageParser.Package setupPackage = getDefaultSystemHandlerActivityPackageLPr( - setupIntent, userId); + PackageParser.Package setupPackage = getSystemPackageLPr( + mService.mSetupWizardPackage); if (setupPackage != null && doesPackageSupportRuntimePermissions(setupPackage)) { grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 58592ff92ce9..0a86a849653c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -64,6 +64,7 @@ import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; +import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.content.pm.PackageManager.MOVE_FAILED_DEVICE_ADMIN; @@ -1034,6 +1035,7 @@ public class PackageManagerService extends IPackageManager.Stub { final @Nullable String mRequiredVerifierPackage; final @Nullable String mRequiredInstallerPackage; + final @Nullable String mSetupWizardPackage; private final PackageUsage mPackageUsage = new PackageUsage(); @@ -2529,6 +2531,7 @@ public class PackageManagerService extends IPackageManager.Stub { } mInstallerService = new PackageInstallerService(context, this); + mSetupWizardPackage = getSetupWizardPackageName(); final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr(); final ComponentName ephemeralInstallerComponent = getEphemeralInstallerLPr(); @@ -2886,12 +2889,15 @@ public class PackageManagerService extends IPackageManager.Stub { return cur; } - PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) { + private PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) { if (!sUserManager.exists(userId)) return null; - final PackageSetting ps = (PackageSetting) p.mExtras; if (ps == null) { return null; } + final PackageParser.Package p = ps.pkg; + if (p == null) { + return null; + } final PermissionsState permissionsState = ps.getPermissionsState(); @@ -2961,14 +2967,28 @@ public class PackageManagerService extends IPackageManager.Stub { false /* requireFullPermission */, false /* checkShell */, "get package info"); // reader synchronized (mPackages) { - PackageParser.Package p = mPackages.get(packageName); + final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0; + PackageParser.Package p = null; + if (matchFactoryOnly) { + final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName); + if (ps != null) { + return generatePackageInfo(ps, flags, userId); + } + } + if (p == null) { + p = mPackages.get(packageName); + if (matchFactoryOnly && !isSystemApp(p)) { + return null; + } + } if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getPackageInfo " + packageName + ": " + p); if (p != null) { - return generatePackageInfo(p, flags, userId); + return generatePackageInfo((PackageSetting)p.mExtras, flags, userId); } - if ((flags & MATCH_UNINSTALLED_PACKAGES) != 0) { - return generatePackageInfoFromSettingsLPw(packageName, flags, userId); + if (!matchFactoryOnly && (flags & MATCH_UNINSTALLED_PACKAGES) != 0) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + return generatePackageInfo(ps, flags, userId); } } return null; @@ -3129,8 +3149,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { if (ps.pkg == null) { - PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, - flags, userId); + final PackageInfo pInfo = generatePackageInfo(ps, flags, userId); if (pInfo != null) { return pInfo.applicationInfo; } @@ -3142,31 +3161,6 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags, - int userId) { - if (!sUserManager.exists(userId)) return null; - PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps != null) { - PackageParser.Package pkg = ps.pkg; - if (pkg == null) { - if ((flags & MATCH_UNINSTALLED_PACKAGES) == 0) { - return null; - } - // Only data remains, so we aren't worried about code paths - pkg = new PackageParser.Package(packageName); - pkg.applicationInfo.packageName = packageName; - pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY; - pkg.applicationInfo.privateFlags = ps.pkgPrivateFlags; - pkg.applicationInfo.uid = ps.appId; - pkg.applicationInfo.initForUser(userId); - pkg.applicationInfo.primaryCpuAbi = ps.primaryCpuAbiString; - pkg.applicationInfo.secondaryCpuAbi = ps.secondaryCpuAbiString; - } - return generatePackageInfo(pkg, flags, userId); - } - return null; - } - @Override public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { if (!sUserManager.exists(userId)) return null; @@ -5915,11 +5909,11 @@ public class PackageManagerService extends IPackageManager.Stub { if (listUninstalled) { list = new ArrayList<PackageInfo>(mSettings.mPackages.size()); for (PackageSetting ps : mSettings.mPackages.values()) { - PackageInfo pi; + final PackageInfo pi; if (ps.pkg != null) { - pi = generatePackageInfo(ps.pkg, flags, userId); + pi = generatePackageInfo(ps, flags, userId); } else { - pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId); + pi = generatePackageInfo(ps, flags, userId); } if (pi != null) { list.add(pi); @@ -5928,7 +5922,8 @@ public class PackageManagerService extends IPackageManager.Stub { } else { list = new ArrayList<PackageInfo>(mPackages.size()); for (PackageParser.Package p : mPackages.values()) { - PackageInfo pi = generatePackageInfo(p, flags, userId); + final PackageInfo pi = + generatePackageInfo((PackageSetting)p.mExtras, flags, userId); if (pi != null) { list.add(pi); } @@ -5955,11 +5950,11 @@ public class PackageManagerService extends IPackageManager.Stub { if (numMatch == 0) { return; } - PackageInfo pi; + final PackageInfo pi; if (ps.pkg != null) { - pi = generatePackageInfo(ps.pkg, flags, userId); + pi = generatePackageInfo(ps, flags, userId); } else { - pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId); + pi = generatePackageInfo(ps, flags, userId); } // The above might return null in cases of uninstalled apps or install-state // skew across users/profiles. @@ -9704,6 +9699,12 @@ public class PackageManagerService extends IPackageManager.Stub { // is granted only if it was already granted. allowed = origPermissions.hasInstallPermission(perm); } + if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0 + && pkg.packageName.equals(mSetupWizardPackage)) { + // If this permission is to be granted to the system setup wizard and + // this app is a setup wizard, then it gets the permission. + allowed = true; + } } return allowed; } @@ -16626,6 +16627,21 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); set, comp, userId); } + private @Nullable String getSetupWizardPackageName() { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_SETUP_WIZARD); + + final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null, + MATCH_SYSTEM_ONLY | MATCH_DISABLED_COMPONENTS, UserHandle.myUserId()); + if (matches.size() == 1) { + return matches.get(0).getComponentInfo().packageName; + } else { + Slog.e(TAG, "There should probably be exactly one setup wizard; found " + matches.size() + + ": matches=" + matches); + return null; + } + } + @Override public void setApplicationEnabledSetting(String appPackageName, int newState, int flags, int userId, String callingPackage) { diff --git a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java b/services/core/java/com/android/server/pm/ShortcutBackupAgent.java deleted file mode 100644 index a0fbc37a4ace..000000000000 --- a/services/core/java/com/android/server/pm/ShortcutBackupAgent.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.pm; - -import android.app.backup.BlobBackupHelper; - -public class ShortcutBackupAgent extends BlobBackupHelper { - private static final String TAG = "ShortcutBackupAgent"; - private static final int BLOB_VERSION = 1; - - public ShortcutBackupAgent(int currentBlobVersion, String... keys) { - super(currentBlobVersion, keys); - } - - @Override - protected byte[] getBackupPayload(String key) { - throw new RuntimeException("not implemented yet"); // todo - } - - @Override - protected void applyRestoredPayload(String key, byte[] payload) { - throw new RuntimeException("not implemented yet"); // todo - } -} diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index 7699f305fb17..c6d66fe7fee5 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -20,6 +20,10 @@ import android.annotation.UserIdInt; import android.content.pm.ShortcutInfo; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.ShortcutUser.PackageWithUser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -27,6 +31,7 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; /** @@ -43,15 +48,16 @@ class ShortcutLauncher extends ShortcutPackageItem { private static final String ATTR_LAUNCHER_USER_ID = "launcher-user"; private static final String ATTR_VALUE = "value"; private static final String ATTR_PACKAGE_NAME = "package-name"; + private static final String ATTR_PACKAGE_USER_ID = "package-user"; private final int mOwnerUserId; /** * Package name -> IDs. */ - final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>(); + final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>(); - public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName, + private ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName, @UserIdInt int launcherUserId, ShortcutPackageInfo spi) { super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty()); mOwnerUserId = ownerUserId; @@ -59,7 +65,7 @@ class ShortcutLauncher extends ShortcutPackageItem { public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName, @UserIdInt int launcherUserId) { - this(launcherUserId, packageName, launcherUserId, null); + this(ownerUserId, packageName, launcherUserId, null); } @Override @@ -67,16 +73,38 @@ class ShortcutLauncher extends ShortcutPackageItem { return mOwnerUserId; } + /** + * Called when the new package can't receive the backup, due to signature or version mismatch. + */ + @Override + protected void onRestoreBlocked(ShortcutService s) { + final ArrayList<PackageWithUser> pinnedPackages = + new ArrayList<>(mPinnedShortcuts.keySet()); + mPinnedShortcuts.clear(); + for (int i = pinnedPackages.size() - 1; i >= 0; i--) { + final PackageWithUser pu = pinnedPackages.get(i); + s.getPackageShortcutsLocked(pu.packageName, pu.userId) + .refreshPinnedFlags(s); + } + } + + @Override + protected void onRestored(ShortcutService s) { + // Nothing to do. + } + public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId, @NonNull String packageName, @NonNull List<String> ids) { final ShortcutPackage packageShortcuts = s.getPackageShortcutsLocked(packageName, packageUserId); + final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName); + final int idSize = ids.size(); if (idSize == 0) { - mPinnedShortcuts.remove(packageName); + mPinnedShortcuts.remove(pu); } else { - final ArraySet<String> prevSet = mPinnedShortcuts.get(packageName); + final ArraySet<String> prevSet = mPinnedShortcuts.get(pu); // Pin shortcuts. Make sure only pin the ones that were visible to the caller. // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here. @@ -93,7 +121,7 @@ class ShortcutLauncher extends ShortcutPackageItem { newSet.add(id); } } - mPinnedShortcuts.put(packageName, newSet); + mPinnedShortcuts.put(pu, newSet); } packageShortcuts.refreshPinnedFlags(s); } @@ -101,12 +129,13 @@ class ShortcutLauncher extends ShortcutPackageItem { /** * Return the pinned shortcut IDs for the publisher package. */ - public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName) { - return mPinnedShortcuts.get(packageName); + public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName, + @UserIdInt int packageUserId) { + return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)); } - boolean cleanUpPackage(String packageName) { - return mPinnedShortcuts.remove(packageName) != null; + boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) { + return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null; } /** @@ -126,9 +155,15 @@ class ShortcutLauncher extends ShortcutPackageItem { getPackageInfo().saveToXml(out); for (int i = 0; i < size; i++) { + final PackageWithUser pu = mPinnedShortcuts.keyAt(i); + + if (forBackup && (pu.userId != getOwnerUserId())) { + continue; // Target package on a different user, skip. (i.e. work profile) + } + out.startTag(null, TAG_PACKAGE); - ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, - mPinnedShortcuts.keyAt(i)); + ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName); + ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId); final ArraySet<String> ids = mPinnedShortcuts.valueAt(i); final int idSize = ids.size(); @@ -157,8 +192,6 @@ class ShortcutLauncher extends ShortcutPackageItem { final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName, launcherUserId); - ShortcutPackageInfo spi = null; - ArraySet<String> ids = null; final int outerDepth = parser.getDepth(); int type; @@ -172,13 +205,17 @@ class ShortcutLauncher extends ShortcutPackageItem { if (depth == outerDepth + 1) { switch (tag) { case ShortcutPackageInfo.TAG_ROOT: - spi = ShortcutPackageInfo.loadFromXml(parser); + ret.getPackageInfo().loadFromXml(parser, fromBackup); continue; case TAG_PACKAGE: { final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_PACKAGE_NAME); + final int packageUserId = fromBackup ? ownerUserId + : ShortcutService.parseIntAttribute(parser, + ATTR_PACKAGE_USER_ID, ownerUserId); ids = new ArraySet<>(); - ret.mPinnedShortcuts.put(packageName, ids); + ret.mPinnedShortcuts.put( + PackageWithUser.of(packageUserId, packageName), ids); continue; } } @@ -186,17 +223,17 @@ class ShortcutLauncher extends ShortcutPackageItem { if (depth == outerDepth + 2) { switch (tag) { case TAG_PIN: { - ids.add(ShortcutService.parseStringAttribute(parser, - ATTR_VALUE)); + if (ids == null) { + Slog.w(TAG, TAG_PIN + " in invalid place"); + } else { + ids.add(ShortcutService.parseStringAttribute(parser, ATTR_VALUE)); + } continue; } } } ShortcutService.warnForInvalidTag(depth, tag); } - if (spi != null) { - ret.replacePackageInfo(spi); - } return ret; } @@ -208,6 +245,8 @@ class ShortcutLauncher extends ShortcutPackageItem { pw.print(getPackageName()); pw.print(" Package user: "); pw.print(getPackageUserId()); + pw.print(" Owner user: "); + pw.print(getOwnerUserId()); pw.println(); getPackageInfo().dump(s, pw, prefix + " "); @@ -217,10 +256,14 @@ class ShortcutLauncher extends ShortcutPackageItem { for (int i = 0; i < size; i++) { pw.println(); + final PackageWithUser pu = mPinnedShortcuts.keyAt(i); + pw.print(prefix); pw.print(" "); pw.print("Package: "); - pw.println(mPinnedShortcuts.keyAt(i)); + pw.print(pu.packageName); + pw.print(" User: "); + pw.println(pu.userId); final ArraySet<String> ids = mPinnedShortcuts.valueAt(i); final int idSize = ids.size(); @@ -233,4 +276,9 @@ class ShortcutLauncher extends ShortcutPackageItem { } } } + + @VisibleForTesting + ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) { + return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName))); + } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 5916202a0bdb..1076a7a0ae88 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -26,6 +26,8 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -34,6 +36,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.function.Predicate; @@ -83,7 +86,7 @@ class ShortcutPackage extends ShortcutPackageItem { */ private long mLastResetTime; - public ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) { + private ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) { super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty()); } @@ -97,6 +100,19 @@ class ShortcutPackage extends ShortcutPackageItem { return getPackageUserId(); } + @Override + protected void onRestoreBlocked(ShortcutService s) { + // Can't restore due to version/signature mismatch. Remove all shortcuts. + mShortcuts.clear(); + } + + @Override + protected void onRestored(ShortcutService s) { + // Because some launchers may not have been restored (e.g. allowBackup=false), + // we need to re-calculate the pinned shortcuts. + refreshPinnedFlags(s); + } + /** * Note this does *not* provide a correct view to the calling launcher. */ @@ -229,20 +245,26 @@ class ShortcutPackage extends ShortcutPackageItem { s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers(); for (int l = launchers.size() - 1; l >= 0; l--) { + // Note even if a launcher that hasn't been installed can still pin shortcuts. + final ShortcutLauncher launcherShortcuts = launchers.valueAt(l); final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds( - getPackageName()); + getPackageName(), getPackageUserId()); if (pinned == null || pinned.size() == 0) { continue; } for (int i = pinned.size() - 1; i >= 0; i--) { - final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i)); + final String id = pinned.valueAt(i); + final ShortcutInfo si = mShortcuts.get(id); if (si == null) { - s.wtf("Shortcut not found"); - } else { - si.addFlags(ShortcutInfo.FLAG_PINNED); + // This happens if a launcher pinned shortcuts from this package, then backup& + // restored, but this package doesn't allow backing up. + // In that case the launcher ends up having a dangling pinned shortcuts. + // That's fine, when the launcher is restored, we'll fix it. + continue; } + si.addFlags(ShortcutInfo.FLAG_PINNED); } } @@ -312,11 +334,15 @@ class ShortcutPackage extends ShortcutPackageItem { public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result, @Nullable Predicate<ShortcutInfo> query, int cloneFlag, @Nullable String callingLauncher, int launcherUserId) { + if (getPackageInfo().isShadow()) { + // Restored and the app not installed yet, so don't return any. + return; + } // Set of pinned shortcuts by the calling launcher. final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null - : s.getLauncherShortcuts(callingLauncher, getPackageUserId(), launcherUserId) - .getPinnedShortcutIds(getPackageName()); + : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) + .getPinnedShortcutIds(getPackageName(), getPackageUserId()); for (int i = 0; i < mShortcuts.size(); i++) { final ShortcutInfo si = mShortcuts.valueAt(i); @@ -328,7 +354,8 @@ class ShortcutPackage extends ShortcutPackageItem { || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId())); if (!si.isDynamic()) { if (!si.isPinned()) { - s.wtf("Shortcut not pinned here"); + s.wtf("Shortcut not pinned: package " + getPackageName() + + ", user=" + getPackageUserId() + ", id=" + si.getId()); continue; } if (!isPinnedByCaller) { @@ -479,7 +506,6 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); - ShortcutPackageInfo spi = null; final int outerDepth = parser.getDepth(); int type; @@ -493,7 +519,7 @@ class ShortcutPackage extends ShortcutPackageItem { if (depth == outerDepth + 1) { switch (tag) { case ShortcutPackageInfo.TAG_ROOT: - spi = ShortcutPackageInfo.loadFromXml(parser); + ret.getPackageInfo().loadFromXml(parser, fromBackup); continue; case TAG_SHORTCUT: final ShortcutInfo si = parseShortcut(parser, packageName); @@ -505,9 +531,6 @@ class ShortcutPackage extends ShortcutPackageItem { } ShortcutService.warnForInvalidTag(depth, tag); } - if (spi != null) { - ret.replacePackageInfo(spi); - } return ret; } @@ -567,4 +590,9 @@ class ShortcutPackage extends ShortcutPackageItem { intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, iconRes, bitmapPath); } + + @VisibleForTesting + List<ShortcutInfo> getAllShortcutsForTest() { + return new ArrayList<>(mShortcuts.values()); + } } diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java index 5f706b83271e..2c45890a9c40 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java @@ -67,10 +67,6 @@ class ShortcutPackageInfo { return mIsShadow; } - public boolean isInstalled() { - return !mIsShadow; - } - public void setShadow(boolean shadow) { mIsShadow = shadow; } @@ -79,14 +75,24 @@ class ShortcutPackageInfo { return mVersionCode; } - public boolean canRestoreTo(PackageInfo target) { + public boolean hasSignatures() { + return mSigHashes.size() > 0; + } + + public boolean canRestoreTo(ShortcutService s, PackageInfo target) { + if (!s.shouldBackupApp(target)) { + // "allowBackup" was true when backed up, but now false. + Slog.w(TAG, "Can't restore: package no longer allows backup"); + return false; + } if (target.versionCode < mVersionCode) { - Slog.w(TAG, String.format("Package current version %d < backed up version %d", + Slog.w(TAG, String.format( + "Can't restore: package current version %d < backed up version %d", target.versionCode, mVersionCode)); return false; } if (!BackupUtils.signaturesMatch(mSigHashes, target)) { - Slog.w(TAG, "Package signature mismtach"); + Slog.w(TAG, "Can't restore: Package signature mismatch"); return false; } return true; @@ -106,6 +112,11 @@ class ShortcutPackageInfo { } public void refresh(ShortcutService s, ShortcutPackageItem pkg) { + if (mIsShadow) { + s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName() + + ", user=" + pkg.getOwnerUserId()); + return; + } // Note use mUserId here, rather than userId. final PackageInfo pi = s.getPackageInfoWithSignatures( pkg.getPackageName(), pkg.getPackageUserId()); @@ -132,14 +143,16 @@ class ShortcutPackageInfo { out.endTag(null, TAG_ROOT); } - public static ShortcutPackageInfo loadFromXml(XmlPullParser parser) + public void loadFromXml(XmlPullParser parser, boolean fromBackup) throws IOException, XmlPullParserException { final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION); - final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); - final ArrayList<byte[]> hashes = new ArrayList<>(); + // When restoring from backup, it's always shadow. + final boolean shadow = + fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); + final ArrayList<byte[]> hashes = new ArrayList<>(); final int outerDepth = parser.getDepth(); int type; @@ -163,7 +176,11 @@ class ShortcutPackageInfo { } ShortcutService.warnForInvalidTag(depth, tag); } - return new ShortcutPackageInfo(versionCode, hashes, shadow); + + // Successfully loaded; replace the feilds. + mVersionCode = versionCode; + mIsShadow = shadow; + mSigHashes = hashes; } public void dump(ShortcutService s, PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index de2709d3d7ac..f31dd17d6288 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -16,6 +16,8 @@ package com.android.server.pm; import android.annotation.NonNull; +import android.content.pm.PackageInfo; +import android.util.Slog; import com.android.internal.util.Preconditions; @@ -25,10 +27,12 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; abstract class ShortcutPackageItem { + private static final String TAG = ShortcutService.TAG; + private final int mPackageUserId; private final String mPackageName; - private ShortcutPackageInfo mPackageInfo; + private final ShortcutPackageInfo mPackageInfo; protected ShortcutPackageItem(int packageUserId, @NonNull String packageName, @NonNull ShortcutPackageInfo packageInfo) { @@ -61,25 +65,62 @@ abstract class ShortcutPackageItem { return mPackageInfo; } - /** - * Should be only used when loading from a file.o - */ - protected void replacePackageInfo(@NonNull ShortcutPackageInfo packageInfo) { - mPackageInfo = Preconditions.checkNotNull(packageInfo); - } - public void refreshPackageInfoAndSave(ShortcutService s) { + if (mPackageInfo.isShadow()) { + return; // Don't refresh for shadow user. + } mPackageInfo.refresh(s, this); s.scheduleSaveUser(getOwnerUserId()); } - public void ensureNotShadowAndSave(ShortcutService s) { - if (mPackageInfo.isShadow()) { - mPackageInfo.setShadow(false); - s.scheduleSaveUser(getOwnerUserId()); + public void attemptToRestoreIfNeededAndSave(ShortcutService s) { + if (!mPackageInfo.isShadow()) { + return; // Already installed, nothing to do. } + if (!s.isPackageInstalled(mPackageName, mPackageUserId)) { + if (ShortcutService.DEBUG) { + Slog.d(TAG, String.format("Package still not installed: %s user=%d", + mPackageName, mPackageUserId)); + } + return; // Not installed, no need to restore yet. + } + if (!mPackageInfo.hasSignatures()) { + s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId + + " but signatures not found in the restore data."); + onRestoreBlocked(s); + return; + } + + final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId); + if (!mPackageInfo.canRestoreTo(s, pi)) { + // Package is now installed, but can't restore. Let the subclass do the cleanup. + onRestoreBlocked(s); + return; + } + if (ShortcutService.DEBUG) { + Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName, + mPackageUserId, getOwnerUserId())); + } + + onRestored(s); + + // Now the package is not shadow. + mPackageInfo.setShadow(false); + + s.scheduleSaveUser(mPackageUserId); } + /** + * Called when the new package can't be restored because it has a lower version number + * or different signatures. + */ + protected abstract void onRestoreBlocked(ShortcutService s); + + /** + * Called when the new package is successfully restored. + */ + protected abstract void onRestored(ShortcutService s); + public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException; } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 76a2dfadc264..7aefcb82a158 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -46,6 +46,7 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; import android.os.Environment; +import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.ParcelFileDescriptor; @@ -101,6 +102,7 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -112,17 +114,16 @@ import java.util.function.Predicate; * * - Scan and remove orphan bitmaps (just in case). * - * - Backup & restore - * * - Detect when already registered instances are passed to APIs again, which might break * internal bitmap handling. + * + * - Add more call stats. */ public class ShortcutService extends IShortcutService.Stub { static final String TAG = "ShortcutService"; static final boolean DEBUG = false; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true - static final boolean ENABLE_DEBUG_COMMAND = true; // STOPSHIP if true @VisibleForTesting static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day @@ -262,6 +263,26 @@ public class ShortcutService extends IShortcutService.Stub { | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_UNINSTALLED_PACKAGES; + // Stats + @VisibleForTesting + interface Stats { + int GET_DEFAULT_HOME = 0; + int GET_PACKAGE_INFO = 1; + int GET_PACKAGE_INFO_WITH_SIG = 2; + int GET_APPLICATION_INFO = 3; + int LAUNCHER_PERMISSION_CHECK = 4; + + int COUNT = LAUNCHER_PERMISSION_CHECK + 1; + } + + final Object mStatLock = new Object(); + + @GuardedBy("mStatLock") + private final int[] mCountStats = new int[Stats.COUNT]; + + @GuardedBy("mStatLock") + private final long[] mDurationStats = new long[Stats.COUNT]; + public ShortcutService(Context context) { this(context, BackgroundThread.get().getLooper()); } @@ -278,6 +299,13 @@ public class ShortcutService extends IShortcutService.Stub { mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false); } + void logDurationStat(int statId, long start) { + synchronized (mStatLock) { + mCountStats[statId]++; + mDurationStats[statId] += (System.currentTimeMillis() - start); + } + } + /** * System service lifecycle. */ @@ -822,7 +850,7 @@ public class ShortcutService extends IShortcutService.Stub { @GuardedBy("mLock") @NonNull - boolean isUserLoadedLocked(@UserIdInt int userId) { + private boolean isUserLoadedLocked(@UserIdInt int userId) { return mUsers.get(userId) != null; } @@ -841,19 +869,27 @@ public class ShortcutService extends IShortcutService.Stub { return userPackages; } + void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) { + for (int i = mUsers.size() - 1; i >= 0; i--) { + c.accept(mUsers.valueAt(i)); + } + } + /** Return the per-user per-package state. */ @GuardedBy("mLock") @NonNull ShortcutPackage getPackageShortcutsLocked( @NonNull String packageName, @UserIdInt int userId) { - return getUserShortcutsLocked(userId).getPackageShortcuts(packageName); + return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName); } @GuardedBy("mLock") @NonNull - ShortcutLauncher getLauncherShortcuts( - @NonNull String packageName, @UserIdInt int userId, @UserIdInt int launcherUserId) { - return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName, launcherUserId); + ShortcutLauncher getLauncherShortcutsLocked( + @NonNull String packageName, @UserIdInt int ownerUserId, + @UserIdInt int launcherUserId) { + return getUserShortcutsLocked(ownerUserId) + .getLauncherShortcuts(this, packageName, launcherUserId); } // === Caller validation === @@ -1212,8 +1248,6 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); - ps.ensureNotShadowAndSave(this); - // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1249,8 +1283,6 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); - ps.ensureNotShadowAndSave(this); - // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1288,8 +1320,6 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); - ps.ensureNotShadowAndSave(this); - // Throttling. if (!ps.tryApiCall(this)) { return false; @@ -1422,15 +1452,17 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) { synchronized (mLock) { - long start = System.currentTimeMillis(); + final long start = System.currentTimeMillis(); final ShortcutUser user = getUserShortcutsLocked(userId); final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); // Default launcher from package manager. + final long startGetHomeActivitiesAsUser = System.currentTimeMillis(); final ComponentName defaultLauncher = injectPackageManagerInternal() .getHomeActivitiesAsUser(allHomeCandidates, userId); + logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser); ComponentName detected; if (defaultLauncher != null) { @@ -1473,10 +1505,8 @@ public class ShortcutService extends IShortcutService.Stub { lastPriority = ri.priority; } } - final long end = System.currentTimeMillis(); - if (DEBUG) { - Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start)); - } + logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start); + if (detected != null) { if (DEBUG) { Slog.v(TAG, "Detected launcher: " + detected); @@ -1492,10 +1522,17 @@ public class ShortcutService extends IShortcutService.Stub { // === House keeping === + /** + * Remove all the information associated with a package. This will really remove all the + * information, including the restore information (i.e. it'll remove packages even if they're + * shadow). + */ @VisibleForTesting void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) { - - // TODO Don't remove shadow packages' information. + if (isPackageInstalled(packageName, packageUserId)) { + wtf("Package " + packageName + " is still installed for user " + packageUserId); + return; + } final boolean wasUserLoaded = isUserLoadedLocked(owningUserId); @@ -1504,7 +1541,7 @@ public class ShortcutService extends IShortcutService.Stub { // First, remove the package from the package list (if the package is a publisher). if (packageUserId == owningUserId) { - if (mUser.getPackages().remove(packageName) != null) { + if (mUser.removePackage(packageName) != null) { doNotify = true; } } @@ -1515,12 +1552,12 @@ public class ShortcutService extends IShortcutService.Stub { // Then remove pinned shortcuts from all launchers. final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = mUser.getAllLaunchers(); for (int i = launchers.size() - 1; i >= 0; i--) { - launchers.valueAt(i).cleanUpPackage(packageName); + launchers.valueAt(i).cleanUpPackage(packageName, packageUserId); } // Now there may be orphan shortcuts because we removed pinned shortucts at the previous // step. Remove them too. - for (int i = mUser.getPackages().size() - 1; i >= 0; i--) { - mUser.getPackages().valueAt(i).refreshPinnedFlags(this); + for (int i = mUser.getAllPackages().size() - 1; i >= 0; i--) { + mUser.getAllPackages().valueAt(i).refreshPinnedFlags(this); } scheduleSaveUser(owningUserId); @@ -1539,6 +1576,7 @@ public class ShortcutService extends IShortcutService.Stub { * Entry point from {@link LauncherApps}. */ private class LocalService extends ShortcutServiceInternal { + @Override public List<ShortcutInfo> getShortcuts(int launcherUserId, @NonNull String callingPackage, long changedSince, @@ -1551,13 +1589,16 @@ public class ShortcutService extends IShortcutService.Stub { : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; synchronized (mLock) { + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(ShortcutService.this); + if (packageName != null) { getShortcutsInnerLocked(launcherUserId, callingPackage, packageName, changedSince, componentName, queryFlags, userId, ret, cloneFlag); } else { final ArrayMap<String, ShortcutPackage> packages = - getUserShortcutsLocked(userId).getPackages(); + getUserShortcutsLocked(userId).getAllPackages(); for (int i = packages.size() - 1; i >= 0; i--) { getShortcutsInnerLocked(launcherUserId, callingPackage, packages.keyAt(i), changedSince, @@ -1601,6 +1642,9 @@ public class ShortcutService extends IShortcutService.Stub { final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size()); final ArraySet<String> idSet = new ArraySet<>(ids); synchronized (mLock) { + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(ShortcutService.this); + getPackageShortcutsLocked(packageName, userId).findAll( ShortcutService.this, ret, (ShortcutInfo si) -> idSet.contains(si.getId()), @@ -1616,13 +1660,16 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); synchronized (mLock) { + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(ShortcutService.this); + final ShortcutInfo si = getShortcutInfoLocked( launcherUserId, callingPackage, packageName, shortcutId, userId); return si != null && si.isPinned(); } } - public ShortcutInfo getShortcutInfoLocked( + private ShortcutInfo getShortcutInfoLocked( int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId) { Preconditions.checkStringNotEmpty(packageName, "packageName"); @@ -1646,9 +1693,8 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutLauncher launcher = - getLauncherShortcuts(callingPackage, userId, launcherUserId); - - launcher.ensureNotShadowAndSave(ShortcutService.this); + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); + launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this); launcher.pinShortcuts( ShortcutService.this, userId, packageName, shortcutIds); @@ -1665,6 +1711,9 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); synchronized (mLock) { + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(ShortcutService.this); + // Make sure the shortcut is actually visible to the launcher. final ShortcutInfo si = getShortcutInfoLocked( launcherUserId, callingPackage, packageName, shortcutId, userId); @@ -1690,6 +1739,9 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkNotNull(shortcut, "shortcut"); synchronized (mLock) { + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(ShortcutService.this); + final ShortcutInfo shortcutInfo = getPackageShortcutsLocked( shortcut.getPackageName(), userId).findShortcutById(shortcut.getId()); return (shortcutInfo != null && shortcutInfo.hasIconResource()) @@ -1704,6 +1756,9 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkNotNull(shortcutIn, "shortcut"); synchronized (mLock) { + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(ShortcutService.this); + final ShortcutInfo shortcutInfo = getPackageShortcutsLocked( shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId()); if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { @@ -1757,28 +1812,28 @@ public class ShortcutService extends IShortcutService.Stub { * perform cleanup. */ @VisibleForTesting - void cleanupGonePackages(@UserIdInt int userId) { + void cleanupGonePackages(@UserIdInt int ownerUserId) { if (DEBUG) { - Slog.d(TAG, "cleanupGonePackages() userId=" + userId); + Slog.d(TAG, "cleanupGonePackages() ownerUserId=" + ownerUserId); } final ArrayList<PackageWithUser> gonePackages = new ArrayList<>(); synchronized (mLock) { - final ShortcutUser user = getUserShortcutsLocked(userId); + final ShortcutUser user = getUserShortcutsLocked(ownerUserId); user.forAllPackageItems(spi -> { if (spi.getPackageInfo().isShadow()) { return; // Don't delete shadow information. } if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) { - return; + return; // Package not gone. } gonePackages.add(PackageWithUser.of(spi)); }); if (gonePackages.size() > 0) { for (int i = gonePackages.size() - 1; i >= 0; i--) { final PackageWithUser pu = gonePackages.get(i); - cleanUpPackageLocked(pu.packageName, userId, pu.userId); + cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId); } } } @@ -1789,7 +1844,8 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); } synchronized (mLock) { - getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId); + forEachLoadedUserLocked(user -> + user.attemptToRestoreIfNeededAndSave(this, packageName, userId)); } } @@ -1798,18 +1854,20 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", packageName, userId)); } - synchronized (mLock) { - getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId); + forEachLoadedUserLocked(user -> + user.attemptToRestoreIfNeededAndSave(this, packageName, userId)); } } - private void handlePackageRemoved(String packageName, @UserIdInt int userId) { + private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) { if (DEBUG) { - Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId)); + Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, + packageUserId)); } synchronized (mLock) { - cleanUpPackageLocked(packageName, userId, userId); + forEachLoadedUserLocked(user -> + cleanUpPackageLocked(packageName, user.getUserId(), packageUserId)); } } @@ -1836,6 +1894,7 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, boolean getSignatures) { + final long start = System.currentTimeMillis(); final long token = injectClearCallingIdentity(); try { return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS @@ -1847,11 +1906,16 @@ public class ShortcutService extends IShortcutService.Stub { return null; } finally { injectRestoreCallingIdentity(token); + + logDurationStat( + (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO), + start); } } @VisibleForTesting ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) { + final long start = System.currentTimeMillis(); final long token = injectClearCallingIdentity(); try { return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); @@ -1861,6 +1925,8 @@ public class ShortcutService extends IShortcutService.Stub { return null; } finally { injectRestoreCallingIdentity(token); + + logDurationStat(Stats.GET_APPLICATION_INFO, start); } } @@ -1869,7 +1935,7 @@ public class ShortcutService extends IShortcutService.Stub { return (ai != null) && ((ai.flags & flags) == flags); } - private boolean isPackageInstalled(String packageName, int userId) { + boolean isPackageInstalled(String packageName, int userId) { return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED); } @@ -1879,8 +1945,12 @@ public class ShortcutService extends IShortcutService.Stub { return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); } + boolean shouldBackupApp(PackageInfo pi) { + return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; + } + @Override - public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException { + public byte[] getBackupPayload(@UserIdInt int userId) { enforceSystem(); if (DEBUG) { Slog.d(TAG, "Backing up user " + userId); @@ -1908,7 +1978,7 @@ public class ShortcutService extends IShortcutService.Stub { } @Override - public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException { + public void applyRestore(byte[] payload, @UserIdInt int userId) { enforceSystem(); if (DEBUG) { Slog.d(TAG, "Restoring user " + userId); @@ -1923,6 +1993,15 @@ public class ShortcutService extends IShortcutService.Stub { } synchronized (mLock) { mUsers.put(userId, user); + + // Then purge all the save images. + final File bitmapPath = getUserBitmapFilePath(userId); + final boolean success = FileUtils.deleteContents(bitmapPath); + if (!success) { + Slog.w(TAG, "Failed to delete " + bitmapPath); + } + + saveUserLocked(userId); } } @@ -1974,9 +2053,19 @@ public class ShortcutService extends IShortcutService.Stub { pw.print(" Icon format: "); pw.print(mIconPersistFormat); pw.print(" Icon quality: "); - pw.print(mIconPersistQuality); + pw.println(mIconPersistQuality); pw.println(); + pw.println(" Stats:"); + synchronized (mStatLock) { + final String p = " "; + dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()"); + dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check"); + + dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()"); + dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)"); + dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo"); + } for (int i = 0; i < mUsers.size(); i++) { pw.println(); @@ -1991,6 +2080,15 @@ public class ShortcutService extends IShortcutService.Stub { return tobj.format("%Y-%m-%d %H:%M:%S"); } + private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) { + pw.print(prefix); + final int count = mCountStats[statId]; + final long dur = mDurationStats[statId]; + pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms", + label, count, dur, + (count == 0 ? 0 : ((double) dur) / count))); + } + // === Shell support === @Override @@ -2202,9 +2300,10 @@ public class ShortcutService extends IShortcutService.Stub { } final void wtf(String message) { - Slog.wtf(TAG, message, /* exception= */ null); + wtf( message, /* exception= */ null); } + // Injection point. void wtf(String message, Exception e) { Slog.wtf(TAG, message, e); } @@ -2275,7 +2374,7 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutUser user = mUsers.get(userId); if (user == null) return null; - final ShortcutPackage pkg = user.getPackages().get(packageName); + final ShortcutPackage pkg = user.getAllPackages().get(packageName); if (pkg == null) return null; return pkg.findShortcutById(shortcutId); diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 487558f81e45..593f607f6268 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -53,8 +53,8 @@ class ShortcutUser { this.packageName = Preconditions.checkNotNull(packageName); } - public static PackageWithUser of(int launcherUserId, String packageName) { - return new PackageWithUser(launcherUserId, packageName); + public static PackageWithUser of(int userId, String packageName) { + return new PackageWithUser(userId, packageName); } public static PackageWithUser of(ShortcutPackageItem spi) { @@ -78,12 +78,12 @@ class ShortcutUser { @Override public String toString() { - return String.format("{Launcher: %d, %s}", userId, packageName); + return String.format("{Package: %d, %s}", userId, packageName); } } @UserIdInt - final int mUserId; + private final int mUserId; private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); @@ -95,10 +95,18 @@ class ShortcutUser { mUserId = userId; } - public ArrayMap<String, ShortcutPackage> getPackages() { + public int getUserId() { + return mUserId; + } + + public ArrayMap<String, ShortcutPackage> getAllPackages() { return mPackages; } + public ShortcutPackage removePackage(@NonNull String packageName) { + return mPackages.remove(packageName); + } + public ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchers() { return mLaunchers; } @@ -113,22 +121,26 @@ class ShortcutUser { return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); } - public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { + public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) { ShortcutPackage ret = mPackages.get(packageName); if (ret == null) { ret = new ShortcutPackage(mUserId, packageName); mPackages.put(packageName, ret); + } else { + ret.attemptToRestoreIfNeededAndSave(s); } return ret; } - public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName, + public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName, @UserIdInt int launcherUserId) { final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); ShortcutLauncher ret = mLaunchers.get(key); if (ret == null) { ret = new ShortcutLauncher(mUserId, packageName, launcherUserId); mLaunchers.put(key, ret); + } else { + ret.attemptToRestoreIfNeededAndSave(s); } return ret; } @@ -148,14 +160,6 @@ class ShortcutUser { } } - public void unshadowPackage(ShortcutService s, @NonNull String packageName, - @UserIdInt int packageUserId) { - forPackageItem(packageName, packageUserId, spi -> { - Slog.i(TAG, String.format("Restoring for %s, user=%d", packageName, packageUserId)); - spi.ensureNotShadowAndSave(s); - }); - } - public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, Consumer<ShortcutPackageItem> callback) { forAllPackageItems(spi -> { @@ -166,6 +170,13 @@ class ShortcutUser { }); } + public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, + @UserIdInt int packageUserId) { + forPackageItem(packageName, packageUserId, spi -> { + spi.attemptToRestoreIfNeededAndSave(s); + }); + } + public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException { out.startTag(null, TAG_ROOT); @@ -229,7 +240,7 @@ class ShortcutUser { s, parser, userId, fromBackup); // Don't use addShortcut(), we don't need to save the icon. - ret.getPackages().put(shortcuts.getPackageName(), shortcuts); + ret.mPackages.put(shortcuts.getPackageName(), shortcuts); continue; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 747e31e5c2a8..14d0457e41c1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3107,10 +3107,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { return -1; } } else if (keyCode == KeyEvent.KEYCODE_SLASH && event.isMetaPressed()) { - if (down) { - if (repeatCount == 0) { - toggleKeyboardShortcutsMenu(event.getDeviceId()); - } + if (down && repeatCount == 0 && !isKeyguardLocked()) { + toggleKeyboardShortcutsMenu(event.getDeviceId()); } } else if (keyCode == KeyEvent.KEYCODE_ASSIST) { if (down) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 1f03c041b477..eea0ca0666f7 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -425,7 +425,7 @@ class Task implements DimLayer.DimLayerUser { if (mFullscreen || !StackId.isTaskResizeableByDockedStack(mStack.mStackId) || displayContent == null - || displayContent.getDockedStackLocked() != null) { + || displayContent.getDockedStackVisibleForUserLocked() != null) { return true; } return false; diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 0bf7102ddeb8..bb0ec4c33049 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -128,6 +128,10 @@ public class TaskStack implements DimLayer.DimLayerUser, // in which case a second window animation would cause jitter. private boolean mFreezeMovementAnimations = false; + // Temporary storage for the new bounds that should be used after the configuration change. + // Will be cleared once the client retrieves the new bounds via getBoundsForNewConfiguration(). + private final Rect mBoundsAfterRotation = new Rect(); + TaskStack(WindowManagerService service, int stackId) { mService = service; mStackId = stackId; @@ -343,28 +347,28 @@ public class TaskStack implements DimLayer.DimLayerUser, setBounds(mTmpRect2); } else { mLastUpdateDisplayInfoRotation = newRotation; - updateBoundsAfterRotation(); + updateBoundsAfterRotation(true); } } - void onConfigurationChanged() { + boolean onConfigurationChanged() { mLastConfigChangedRotation = getDisplayInfo().rotation; - updateBoundsAfterRotation(); + return updateBoundsAfterRotation(false); } - void updateBoundsAfterRotation() { + boolean updateBoundsAfterRotation(boolean scheduleResize) { if (mLastConfigChangedRotation != mLastUpdateDisplayInfoRotation) { // We wait for the rotation values after configuration change and display info. update // to be equal before updating the bounds due to rotation change otherwise things might // get out of alignment... - return; + return false; } final int newRotation = getDisplayInfo().rotation; if (mRotation == newRotation) { // Nothing to do here if the rotation didn't change - return; + return false; } mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2); @@ -373,11 +377,22 @@ public class TaskStack implements DimLayer.DimLayerUser, snapDockedStackAfterRotation(mTmpRect2); } - // Post message to inform activity manager of the bounds change simulating - // a one-way call. We do this to prevent a deadlock between window manager - // lock and activity manager lock been held. - mService.mH.obtainMessage( - RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mTmpRect2).sendToTarget(); + if (scheduleResize) { + // Post message to inform activity manager of the bounds change simulating + // a one-way call. We do this to prevent a deadlock between window manager + // lock and activity manager lock been held. + mService.mH.obtainMessage(RESIZE_STACK, mStackId, + 0 /*allowResizeInDockedMode*/, mTmpRect2).sendToTarget(); + } else { + mBoundsAfterRotation.set(mTmpRect2); + } + + return true; + } + + void getBoundsForNewConfiguration(Rect outBounds) { + outBounds.set(mBoundsAfterRotation); + mBoundsAfterRotation.setEmpty(); } /** @@ -869,6 +884,10 @@ public class TaskStack implements DimLayer.DimLayerUser, } } + boolean isAdjustedForMinimizedDock() { + return mMinimizeAmount != 0f; + } + private boolean adjustForIME(final WindowState imeWin) { final int dockedSide = getDockSide(); final boolean dockedTopOrBottom = dockedSide == DOCKED_TOP || dockedSide == DOCKED_BOTTOM; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index dcb4a63a560a..14ae74f3967d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -125,6 +125,7 @@ import com.android.internal.R; import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IShortcutService; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; @@ -515,6 +516,8 @@ public class WindowManagerService extends IWindowManager.Stub private final SparseIntArray mTmpTaskIds = new SparseIntArray(); + private final ArrayList<Integer> mChangedStackList = new ArrayList(); + boolean mForceResizableTasks = false; int getDragLayerLocked() { @@ -3398,7 +3401,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (isStackVisibleLocked(DOCKED_STACK_ID) + if ((isStackVisibleLocked(DOCKED_STACK_ID) + && !mStackIdToStack.get(DOCKED_STACK_ID).isAdjustedForMinimizedDock()) || isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) { // We don't let app affect the system orientation when in freeform or docked mode since // they don't occupy the entire display and their request can conflict with other apps. @@ -3580,7 +3584,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setNewConfiguration(Configuration config) { + public int[] setNewConfiguration(Configuration config) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setNewConfiguration()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3592,16 +3596,30 @@ public class WindowManagerService extends IWindowManager.Stub mWaitingForConfig = false; mLastFinishedFreezeSource = "new-config"; } - onConfigurationChanged(); - mWindowPlacerLocked.performSurfacePlacement(); + return onConfigurationChanged(); } } - private void onConfigurationChanged() { + @Override + public Rect getBoundsForNewConfiguration(int stackId) { + synchronized(mWindowMap) { + final TaskStack stack = mStackIdToStack.get(stackId); + final Rect outBounds = new Rect(); + stack.getBoundsForNewConfiguration(outBounds); + return outBounds; + } + } + + private int[] onConfigurationChanged() { + mChangedStackList.clear(); for (int stackNdx = mStackIdToStack.size() - 1; stackNdx >= 0; stackNdx--) { final TaskStack stack = mStackIdToStack.valueAt(stackNdx); - stack.onConfigurationChanged(); + if (stack.onConfigurationChanged()) { + mChangedStackList.add(stack.mStackId); + } } + return mChangedStackList.isEmpty() ? + null : ArrayUtils.convertToIntArray(mChangedStackList); } @Override diff --git a/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java new file mode 100644 index 000000000000..83a59fd85d29 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.notification; + + +import android.app.ActivityManager; +import android.app.Notification; +import android.app.Notification.Builder; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Handler; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.Vibrator; +import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.StatusBarNotification; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class BuzzBeepBlinkTest extends AndroidTestCase { + + @Mock AudioManager mAudioManager; + @Mock Vibrator mVibrator; + @Mock android.media.IRingtonePlayer mRingtonePlayer; + @Mock Handler mHandler; + + private NotificationManagerService mService; + private String mPkg = "com.android.server.notification"; + private int mId = 1001; + private int mOtherId = 1002; + private String mTag = null; + private int mUid = 1000; + private int mPid = 2000; + private int mScore = 10; + private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser()); + + @Override + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); + when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); + + mService = new NotificationManagerService(getContext()); + mService.setAudioManager(mAudioManager); + mService.setVibrator(mVibrator); + mService.setSystemReady(true); + mService.setHandler(mHandler); + } + + // + // Convenience functions for creating notification records + // + + private NotificationRecord getNoisyOtherNotification() { + return getNotificationRecord(mOtherId, false /* insistent */, false /* once */, + true /* noisy */, true /* buzzy*/); + } + + private NotificationRecord getBeepyNotification() { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/); + } + + private NotificationRecord getBeepyOnceNotification() { + return getNotificationRecord(mId, false /* insistent */, true /* once */, + true /* noisy */, false /* buzzy*/); + } + + private NotificationRecord getQuietNotification() { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + false /* noisy */, false /* buzzy*/); + } + + private NotificationRecord getQuietOtherNotification() { + return getNotificationRecord(mOtherId, false /* insistent */, false /* once */, + false /* noisy */, false /* buzzy*/); + } + + private NotificationRecord getQuietOnceNotification() { + return getNotificationRecord(mId, false /* insistent */, true /* once */, + false /* noisy */, false /* buzzy*/); + } + + private NotificationRecord getInsistentBeepyNotification() { + return getNotificationRecord(mId, true /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/); + } + + private NotificationRecord getBuzzyNotification() { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + false /* noisy */, true /* buzzy*/); + } + + private NotificationRecord getBuzzyOnceNotification() { + return getNotificationRecord(mId, false /* insistent */, true /* once */, + false /* noisy */, true /* buzzy*/); + } + + private NotificationRecord getInsistentBuzzyNotification() { + return getNotificationRecord(mId, true /* insistent */, false /* once */, + false /* noisy */, true /* buzzy*/); + } + + private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, + boolean noisy, boolean buzzy) { + final Builder builder = new Builder(getContext()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setPriority(Notification.PRIORITY_HIGH) + .setOnlyAlertOnce(once); + + int defaults = 0; + if (noisy) { + defaults |= Notification.DEFAULT_SOUND; + } + if (buzzy) { + defaults |= Notification.DEFAULT_VIBRATE; + } + builder.setDefaults(defaults); + + Notification n = builder.build(); + if (insistent) { + n.flags |= Notification.FLAG_INSISTENT; + } + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, mPid, + mScore, n, mUser, System.currentTimeMillis()); + return new NotificationRecord(getContext(), sbn); + } + + // + // Convenience functions for interacting with mocks + // + + private void verifyNeverBeep() throws RemoteException { + verify(mRingtonePlayer, never()).playAsync((Uri) anyObject(), (UserHandle) anyObject(), + anyBoolean(), (AudioAttributes) anyObject()); + } + + private void verifyBeep() throws RemoteException { + verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(), + eq(true), (AudioAttributes) anyObject()); + } + + private void verifyBeepLooped() throws RemoteException { + verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(), + eq(false), (AudioAttributes) anyObject()); + } + + private void verifyNeverStopAudio() throws RemoteException { + verify(mRingtonePlayer, never()).stopAsync(); + } + + private void verifyStopAudio() throws RemoteException { + verify(mRingtonePlayer, times(1)).stopAsync(); + } + + private void verifyNeverVibrate() { + verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(), + anyInt(), (AudioAttributes) anyObject()); + } + + private void verifyVibrate() { + verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(), + eq(-1), (AudioAttributes) anyObject()); + } + + private void verifyVibrateLooped() { + verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(), + eq(0), (AudioAttributes) anyObject()); + } + + private void verifyStopVibrate() { + verify(mVibrator, times(1)).cancel(); + } + + private void verifyNeverStopVibrate() throws RemoteException { + verify(mVibrator, never()).cancel(); + } + + @SmallTest + public void testBeep() throws Exception { + NotificationRecord r = getBeepyNotification(); + + mService.buzzBeepBlinkLocked(r); + + verifyBeepLooped(); + verifyNeverVibrate(); + } + + // + // Tests + // + + @SmallTest + public void testBeepInsistently() throws Exception { + NotificationRecord r = getInsistentBeepyNotification(); + + mService.buzzBeepBlinkLocked(r); + + verifyBeep(); + } + + @SmallTest + public void testNoInterruptionForMin() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setImportance(Ranking.IMPORTANCE_MIN, "foo"); + + mService.buzzBeepBlinkLocked(r); + + verifyNeverBeep(); + verifyNeverVibrate(); + } + + @SmallTest + public void testNoInterruptionForIntercepted() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setIntercepted(true); + + mService.buzzBeepBlinkLocked(r); + + verifyNeverBeep(); + verifyNeverVibrate(); + } + + @SmallTest + public void testBeepTwice() throws Exception { + NotificationRecord r = getBeepyNotification(); + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mRingtonePlayer); + + // update should beep + r.isUpdate = true; + mService.buzzBeepBlinkLocked(r); + verifyBeepLooped(); + } + + @SmallTest + public void testHonorAlertOnlyOnceForBeep() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getBeepyOnceNotification(); + s.isUpdate = true; + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mRingtonePlayer); + + // update should not beep + mService.buzzBeepBlinkLocked(s); + verifyNeverBeep(); + } + + @SmallTest + public void testNoisyUpdateDoesNotCancelAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + + mService.buzzBeepBlinkLocked(r); + r.isUpdate = true; + mService.buzzBeepBlinkLocked(r); + + verifyNeverStopAudio(); + } + + @SmallTest + public void testNoisyOnceUpdateDoesNotCancelAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getBeepyOnceNotification(); + s.isUpdate = true; + + mService.buzzBeepBlinkLocked(r); + mService.buzzBeepBlinkLocked(s); + + verifyNeverStopAudio(); + } + + @SmallTest + public void testQuietUpdateDoesNotCancelAudioFromOther() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getQuietNotification(); + s.isUpdate = true; + NotificationRecord other = getNoisyOtherNotification(); + + // set up internal state + mService.buzzBeepBlinkLocked(r); + mService.buzzBeepBlinkLocked(other); // this takes the audio stream + Mockito.reset(mRingtonePlayer); + + // should not stop noise, since we no longer own it + mService.buzzBeepBlinkLocked(s); // this no longer owns the stream + verifyNeverStopAudio(); + } + + @SmallTest + public void testQuietInterloperDoesNotCancelAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord other = getQuietOtherNotification(); + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mRingtonePlayer); + + // should not stop noise, since it does not own it + mService.buzzBeepBlinkLocked(other); + verifyNeverStopAudio(); + } + + @SmallTest + public void testQuietUpdateCancelsAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getQuietNotification(); + s.isUpdate = true; + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mRingtonePlayer); + + // quiet update should stop making noise + mService.buzzBeepBlinkLocked(s); + verifyStopAudio(); + } + + @SmallTest + public void testQuietOnceUpdateCancelsAudio() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getQuietOnceNotification(); + s.isUpdate = true; + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mRingtonePlayer); + + // stop making noise - this is a weird corner case, but quiet should override once + mService.buzzBeepBlinkLocked(s); + verifyStopAudio(); + } + + @SmallTest + public void testDemoteSoundToVibrate() throws Exception { + NotificationRecord r = getBeepyNotification(); + + // the phone is quiet + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + + mService.buzzBeepBlinkLocked(r); + + verifyNeverBeep(); + verifyVibrate(); + } + + @SmallTest + public void testDemotInsistenteSoundToVibrate() throws Exception { + NotificationRecord r = getInsistentBeepyNotification(); + + // the phone is quiet + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + + mService.buzzBeepBlinkLocked(r); + + verifyVibrateLooped(); + } + + @SmallTest + public void testVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + + mService.buzzBeepBlinkLocked(r); + + verifyNeverBeep(); + verifyVibrate(); + } + + @SmallTest + public void testInsistenteVibrate() throws Exception { + NotificationRecord r = getInsistentBuzzyNotification(); + + mService.buzzBeepBlinkLocked(r); + verifyVibrateLooped(); + } + + @SmallTest + public void testVibratTwice() throws Exception { + NotificationRecord r = getBuzzyNotification(); + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mVibrator); + + // update should vibrate + r.isUpdate = true; + mService.buzzBeepBlinkLocked(r); + verifyVibrate(); + } + + @SmallTest + public void testHonorAlertOnlyOnceForBuzz() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getBuzzyOnceNotification(); + s.isUpdate = true; + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mVibrator); + + // update should not beep + mService.buzzBeepBlinkLocked(s); + verifyNeverVibrate(); + } + + @SmallTest + public void testNoisyUpdateDoesNotCancelVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + + mService.buzzBeepBlinkLocked(r); + r.isUpdate = true; + mService.buzzBeepBlinkLocked(r); + + verifyNeverStopVibrate(); + } + + @SmallTest + public void testNoisyOnceUpdateDoesNotCancelVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getBuzzyOnceNotification(); + s.isUpdate = true; + + mService.buzzBeepBlinkLocked(r); + mService.buzzBeepBlinkLocked(s); + + verifyNeverStopVibrate(); + } + + @SmallTest + public void testQuietUpdateDoesNotCancelVibrateFromOther() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getQuietNotification(); + s.isUpdate = true; + NotificationRecord other = getNoisyOtherNotification(); + + // set up internal state + mService.buzzBeepBlinkLocked(r); + mService.buzzBeepBlinkLocked(other); // this takes the vibrate stream + Mockito.reset(mVibrator); + + // should not stop vibrate, since we no longer own it + mService.buzzBeepBlinkLocked(s); // this no longer owns the stream + verifyNeverStopVibrate(); + } + + @SmallTest + public void testQuietInterloperDoesNotCancelVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord other = getQuietOtherNotification(); + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mVibrator); + + // should not stop noise, since it does not own it + mService.buzzBeepBlinkLocked(other); + verifyNeverStopVibrate(); + } + + @SmallTest + public void testQuietUpdateCancelsVibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getQuietNotification(); + s.isUpdate = true; + + // set up internal state + mService.buzzBeepBlinkLocked(r); + + // quiet update should stop making noise + mService.buzzBeepBlinkLocked(s); + verifyStopVibrate(); + } + + @SmallTest + public void testQuietOnceUpdateCancelsvibrate() throws Exception { + NotificationRecord r = getBuzzyNotification(); + NotificationRecord s = getQuietOnceNotification(); + s.isUpdate = true; + + // set up internal state + mService.buzzBeepBlinkLocked(r); + Mockito.reset(mVibrator); + + // stop making noise - this is a weird corner case, but quiet should override once + mService.buzzBeepBlinkLocked(s); + verifyStopVibrate(); + } + + @SmallTest + public void testQuietUpdateCancelsDemotedVibrate() throws Exception { + NotificationRecord r = getBeepyNotification(); + NotificationRecord s = getQuietNotification(); + + // the phone is quiet + when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0); + when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); + + mService.buzzBeepBlinkLocked(r); + + // quiet update should stop making noise + mService.buzzBeepBlinkLocked(s); + verifyStopVibrate(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java deleted file mode 100644 index c44ffa481f8b..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java +++ /dev/null @@ -1,271 +0,0 @@ - -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.pm; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ShortcutInfo; -import android.graphics.drawable.Icon; -import android.os.Bundle; -import android.os.Parcel; -import android.os.PersistableBundle; -import android.test.AndroidTestCase; - -import com.android.internal.util.Preconditions; -import com.android.server.testutis.TestUtils; - -/** - * Tests for {@link ShortcutInfo}. - - m FrameworksServicesTests && - adb install \ - -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && - adb shell am instrument -e class com.android.server.pm.ShortcutInfoTest \ - -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner - - */ -public class ShortcutInfoTest extends AndroidTestCase { - - public void testMissingMandatoryFields() { - TestUtils.assertExpectException( - IllegalArgumentException.class, - "ID must be provided", - () -> new ShortcutInfo.Builder(mContext).build()); - TestUtils.assertExpectException( - IllegalArgumentException.class, - "title must be provided", - () -> new ShortcutInfo.Builder(mContext).setId("id").build() - .enforceMandatoryFields()); - TestUtils.assertExpectException( - NullPointerException.class, - "Intent must be provided", - () -> new ShortcutInfo.Builder(mContext).setId("id").setTitle("x").build() - .enforceMandatoryFields()); - } - - private ShortcutInfo parceled(ShortcutInfo si) { - Parcel p = Parcel.obtain(); - p.writeParcelable(si, 0); - p.setDataPosition(0); - ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader()); - p.recycle(); - return si2; - } - - private Intent makeIntent(String action, Object... bundleKeysAndValues) { - final Intent intent = new Intent(action); - intent.replaceExtras(ShortcutManagerTest.makeBundle(bundleKeysAndValues)); - return intent; - } - - public void testParcel() { - ShortcutInfo si = parceled(new ShortcutInfo.Builder(getContext()) - .setId("id") - .setTitle("title") - .setIntent(makeIntent("action")) - .build()); - assertEquals(getContext().getPackageName(), si.getPackageName()); - assertEquals("id", si.getId()); - assertEquals("title", si.getTitle()); - assertEquals("action", si.getIntent().getAction()); - - PersistableBundle pb = new PersistableBundle(); - pb.putInt("k", 1); - - si = new ShortcutInfo.Builder(getContext()) - .setId("id") - .setActivityComponent(new ComponentName("a", "b")) - .setIcon(Icon.createWithContentUri("content://a.b.c/")) - .setTitle("title") - .setText("text") - .setIntent(makeIntent("action", "key", "val")) - .setWeight(123) - .setExtras(pb) - .build(); - si.addFlags(ShortcutInfo.FLAG_PINNED); - si.setBitmapPath("abc"); - si.setIconResourceId(456); - - si = parceled(si); - - assertEquals(getContext().getPackageName(), si.getPackageName()); - assertEquals("id", si.getId()); - assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); - assertEquals("content://a.b.c/", si.getIcon().getUriString()); - assertEquals("title", si.getTitle()); - assertEquals("text", si.getText()); - assertEquals("action", si.getIntent().getAction()); - assertEquals("val", si.getIntent().getStringExtra("key")); - assertEquals(123, si.getWeight()); - assertEquals(1, si.getExtras().getInt("k")); - - assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); - assertEquals("abc", si.getBitmapPath()); - assertEquals(456, si.getIconResourceId()); - } - - public void testClone() { - PersistableBundle pb = new PersistableBundle(); - pb.putInt("k", 1); - ShortcutInfo sorig = new ShortcutInfo.Builder(getContext()) - .setId("id") - .setActivityComponent(new ComponentName("a", "b")) - .setIcon(Icon.createWithContentUri("content://a.b.c/")) - .setTitle("title") - .setText("text") - .setIntent(makeIntent("action", "key", "val")) - .setWeight(123) - .setExtras(pb) - .build(); - sorig.addFlags(ShortcutInfo.FLAG_PINNED); - sorig.setBitmapPath("abc"); - sorig.setIconResourceId(456); - - ShortcutInfo si = sorig.clone(/* clone flags*/ 0); - - assertEquals(getContext().getPackageName(), si.getPackageName()); - assertEquals("id", si.getId()); - assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); - assertEquals("content://a.b.c/", si.getIcon().getUriString()); - assertEquals("title", si.getTitle()); - assertEquals("text", si.getText()); - assertEquals("action", si.getIntent().getAction()); - assertEquals("val", si.getIntent().getStringExtra("key")); - assertEquals(123, si.getWeight()); - assertEquals(1, si.getExtras().getInt("k")); - - assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); - assertEquals("abc", si.getBitmapPath()); - assertEquals(456, si.getIconResourceId()); - - si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR); - - assertEquals(getContext().getPackageName(), si.getPackageName()); - assertEquals("id", si.getId()); - assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); - assertEquals(null, si.getIcon()); - assertEquals("title", si.getTitle()); - assertEquals("text", si.getText()); - assertEquals("action", si.getIntent().getAction()); - assertEquals("val", si.getIntent().getStringExtra("key")); - assertEquals(123, si.getWeight()); - assertEquals(1, si.getExtras().getInt("k")); - - assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); - assertEquals(null, si.getBitmapPath()); - assertEquals(0, si.getIconResourceId()); - - si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); - - assertEquals(getContext().getPackageName(), si.getPackageName()); - assertEquals("id", si.getId()); - assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); - assertEquals(null, si.getIcon()); - assertEquals("title", si.getTitle()); - assertEquals("text", si.getText()); - assertEquals(null, si.getIntent()); - assertEquals(123, si.getWeight()); - assertEquals(1, si.getExtras().getInt("k")); - - assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); - assertEquals(null, si.getBitmapPath()); - assertEquals(0, si.getIconResourceId()); - - si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); - - assertEquals(getContext().getPackageName(), si.getPackageName()); - assertEquals("id", si.getId()); - assertEquals(null, si.getActivityComponent()); - assertEquals(null, si.getIcon()); - assertEquals(null, si.getTitle()); - assertEquals(null, si.getText()); - assertEquals(null, si.getIntent()); - assertEquals(0, si.getWeight()); - assertEquals(null, si.getExtras()); - - assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags()); - assertEquals(null, si.getBitmapPath()); - assertEquals(0, si.getIconResourceId()); - } - - - public void testCopyNonNullFieldsFrom() { - PersistableBundle pb = new PersistableBundle(); - pb.putInt("k", 1); - ShortcutInfo sorig = new ShortcutInfo.Builder(getContext()) - .setId("id") - .setActivityComponent(new ComponentName("a", "b")) - .setIcon(Icon.createWithContentUri("content://a.b.c/")) - .setTitle("title") - .setText("text") - .setIntent(makeIntent("action", "key", "val")) - .setWeight(123) - .setExtras(pb) - .build(); - sorig.addFlags(ShortcutInfo.FLAG_PINNED); - sorig.setBitmapPath("abc"); - sorig.setIconResourceId(456); - - ShortcutInfo si; - - si = sorig.clone(/* flags=*/ 0); - si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") - .setActivityComponent(new ComponentName("x", "y")).build()); - assertEquals(new ComponentName("x", "y"), si.getActivityComponent()); - - si = sorig.clone(/* flags=*/ 0); - si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") - .setIcon(Icon.createWithContentUri("content://x.y.z/")).build()); - assertEquals("content://x.y.z/", si.getIcon().getUriString()); - - si = sorig.clone(/* flags=*/ 0); - si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") - .setTitle("xyz").build()); - assertEquals("xyz", si.getTitle()); - - si = sorig.clone(/* flags=*/ 0); - si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") - .setText("xxx").build()); - assertEquals("xxx", si.getText()); - - si = sorig.clone(/* flags=*/ 0); - si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") - .setIntent(makeIntent("action2")).build()); - assertEquals("action2", si.getIntent().getAction()); - assertEquals(null, si.getIntent().getStringExtra("key")); - - si = sorig.clone(/* flags=*/ 0); - si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") - .setIntent(makeIntent("action3", "key", "x")).build()); - assertEquals("action3", si.getIntent().getAction()); - assertEquals("x", si.getIntent().getStringExtra("key")); - - si = sorig.clone(/* flags=*/ 0); - si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") - .setWeight(999).build()); - assertEquals(999, si.getWeight()); - - - PersistableBundle pb2 = new PersistableBundle(); - pb2.putInt("x", 99); - - si = sorig.clone(/* flags=*/ 0); - si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") - .setExtras(pb2).build()); - assertEquals(99, si.getExtras().getInt("x")); - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index 5d2924283ef7..0e2a80c8b302 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -57,7 +57,9 @@ import android.os.Bundle; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; +import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; @@ -76,6 +78,7 @@ import com.android.server.pm.LauncherAppsService.LauncherAppsImpl; import com.android.server.pm.ShortcutService.ConfigConstants; import com.android.server.pm.ShortcutService.FileOutputStreamWithPath; import com.android.server.pm.ShortcutUser.PackageWithUser; +import com.android.server.testutis.TestUtils; import libcore.io.IoUtils; @@ -96,6 +99,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; /** * Tests for ShortcutService and ShortcutManager. @@ -107,10 +111,8 @@ import java.util.Set; -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner * TODO: Add checks with assertAllNotHaveIcon() - * - * TODO: separate, detailed tests for ShortcutInfo (CTS?) * - * - * TODO: Cross-user test (do in CTS?) + * TODO: Detailed test for hasShortcutPermissionInner(). + * TODO: Add tests for the command line functions too. */ @SmallTest public class ShortcutManagerTest extends InstrumentationTestCase { @@ -122,6 +124,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { */ private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true + private static final boolean DUMP_ON_TEARDOWN = false; // DO NOT SUBMIT WITH true + // public for mockito public class BaseContext extends MockContext { @Override @@ -261,7 +265,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { @Override boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { // Sort of hack; do a simpler check. - return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage); + return LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage) + || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage); } @Override @@ -394,7 +399,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private Map<String, PackageInfo> mInjectedPackages; - private ArrayList<PackageWithUser> mUninstalledPackages; + private Set<PackageWithUser> mUninstalledPackages; private PackageManager mMockPackageManager; private PackageManagerInternal mMockPackageManagerInternal; @@ -409,12 +414,21 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private static final String CALLING_PACKAGE_3 = "com.android.test.3"; private static final int CALLING_UID_3 = 10003; + private static final String CALLING_PACKAGE_4 = "com.android.test.4"; + private static final int CALLING_UID_4 = 10004; + private static final String LAUNCHER_1 = "com.android.launcher.1"; private static final int LAUNCHER_UID_1 = 10011; private static final String LAUNCHER_2 = "com.android.launcher.2"; private static final int LAUNCHER_UID_2 = 10012; + private static final String LAUNCHER_3 = "com.android.launcher.3"; + private static final int LAUNCHER_UID_3 = 10013; + + private static final String LAUNCHER_4 = "com.android.launcher.4"; + private static final int LAUNCHER_UID_4 = 10014; + private static final int USER_0 = UserHandle.USER_SYSTEM; private static final int USER_10 = 10; private static final int USER_11 = 11; @@ -438,6 +452,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private static final int MAX_ICON_DIMENSION_LOWRAM = 32; + private static final ShortcutQuery QUERY_ALL = new ShortcutQuery(); + + static { + QUERY_ALL.setQueryFlags( + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED); + } + @Override protected void setUp() throws Exception { super.setUp(); @@ -457,10 +478,19 @@ public class ShortcutManagerTest extends InstrumentationTestCase { addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1); addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2); addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3); + addPackage(CALLING_PACKAGE_4, CALLING_UID_4, 10); addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4); addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5); + addPackage(LAUNCHER_3, LAUNCHER_UID_3, 6); + addPackage(LAUNCHER_4, LAUNCHER_UID_4, 10); + + // CALLING_PACKAGE_3 / LAUNCHER_3 are not backup target. + updatePackageInfo(CALLING_PACKAGE_3, + pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); + updatePackageInfo(LAUNCHER_3, + pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); - mUninstalledPackages = new ArrayList<>(); + mUninstalledPackages = new HashSet<>(); mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files"); @@ -475,6 +505,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { setCaller(CALLING_PACKAGE_1); } + @Override + protected void tearDown() throws Exception { + if (DUMP_ON_TEARDOWN) dumpsysOnLogcat("Teardown"); + + super.tearDown(); + } + private Context getTestContext() { return getInstrumentation().getContext(); } @@ -504,6 +541,10 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return Arrays.asList(array); } + private <T> Set<T> set(Set<T> in) { + return new ArraySet<T>(in); + } + private Signature[] genSignatures(String... signatures) { final Signature[] sigs = new Signature[signatures.length]; for (int i = 0; i < signatures.length; i++){ @@ -529,10 +570,24 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures)); } + private void updatePackageInfo(String packageName, Consumer<PackageInfo> c) { + c.accept(mInjectedPackages.get(packageName)); + } + private void uninstallPackage(int userId, String packageName) { + if (ENABLE_DUMP) { + Log.i(TAG, "Unnstall package " + packageName + " / " + userId); + } mUninstalledPackages.add(PackageWithUser.of(userId, packageName)); } + private void installPackage(int userId, String packageName) { + if (ENABLE_DUMP) { + Log.i(TAG, "Install package " + packageName + " / " + userId); + } + mUninstalledPackages.remove(PackageWithUser.of(userId, packageName)); + } + PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId, boolean getSignatures) { final PackageInfo pi = mInjectedPackages.get(packageName); @@ -591,14 +646,22 @@ public class ShortcutManagerTest extends InstrumentationTestCase { /** For debugging */ private void dumpsysOnLogcat() { - if (!ENABLE_DUMP) return; + dumpsysOnLogcat(""); + } + + private void dumpsysOnLogcat(String message) { + dumpsysOnLogcat(message, false); + } + + private void dumpsysOnLogcat(String message, boolean force) { + if (force || !ENABLE_DUMP) return; final ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintWriter pw = new PrintWriter(out); mService.dumpInner(pw); pw.close(); - Log.e(TAG, "Dumping ShortcutService:"); + Log.e(TAG, "Dumping ShortcutService: " + message); for (String line : out.toString().split("\n")) { Log.e(TAG, line); } @@ -608,9 +671,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { * For debugging, dump arbitrary file on logcat. */ private void dumpFileOnLogcat(String path) { + dumpFileOnLogcat(path, ""); + } + + private void dumpFileOnLogcat(String path, String message) { if (!ENABLE_DUMP) return; - Log.i(TAG, "Dumping file: " + path); + Log.i(TAG, "Dumping file: " + path + " " + message); final StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new FileReader(path))) { String line; @@ -636,10 +703,14 @@ public class ShortcutManagerTest extends InstrumentationTestCase { * For debugging, dump per-user state file on logcat. */ private void dumpUserFile(int userId) { + dumpUserFile(userId, ""); + } + + private void dumpUserFile(int userId, String message) { mService.saveDirtyInfo(); dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath() + "/user-" + userId - + "/" + ShortcutService.FILENAME_USER_PACKAGES); + + "/" + ShortcutService.FILENAME_USER_PACKAGES, message); } private void waitOnMainThread() throws Throwable { @@ -1055,11 +1126,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return i; } - /** - * Wrap a set in an ArraySet just to get a better toString. - */ - private <T> Set<T> set(Set<T> in) { - return new ArraySet<T>(in); + private ShortcutInfo parceled(ShortcutInfo si) { + Parcel p = Parcel.obtain(); + p.writeParcelable(si, 0); + p.setDataPosition(0); + ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader()); + p.recycle(); + return si2; } /** @@ -1320,8 +1393,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Still 2 calls left. assertEquals(2, mManager.getRemainingCallCount()); - - // TODO Make sure pinned shortcuts won't be deleted. } public void testDeleteAllDynamicShortcuts() { @@ -1351,8 +1422,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Still 1 call left assertEquals(1, mManager.getRemainingCallCount()); - - // TODO Make sure pinned shortcuts won't be deleted. } public void testThrottling() { @@ -1877,9 +1946,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { }); } - // TODO: updateShortcuts() - // TODO: getPinnedShortcuts() - // === Test for launcher side APIs === private static ShortcutQuery buildQuery(long changedSince, @@ -1893,6 +1959,20 @@ public class ShortcutManagerTest extends InstrumentationTestCase { return q; } + private static ShortcutQuery buildAllQuery(String packageName) { + final ShortcutQuery q = new ShortcutQuery(); + q.setPackage(packageName); + q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED); + return q; + } + + private static ShortcutQuery buildPinnedQuery(String packageName) { + final ShortcutQuery q = new ShortcutQuery(); + q.setPackage(packageName); + q.setQueryFlags(ShortcutQuery.FLAG_GET_PINNED); + return q; + } + public void testGetShortcuts() { // Set up shortcuts. @@ -3052,6 +3132,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Remove CALLING_PACKAGE_2 reset(c0); + uninstallPackage(USER_0, CALLING_PACKAGE_2); mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0); // Should get a callback with an empty list. @@ -3299,9 +3380,9 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Check the registered packages. dumpsysOnLogcat(); assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user0.getPackages().keySet())); + set(user0.getAllPackages().keySet())); assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getPackages().keySet())); + set(user10.getAllPackages().keySet())); assertEquals( makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), @@ -3326,13 +3407,14 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.saveDirtyInfo(); // Nonexistent package. + uninstallPackage(USER_0, "abc"); mService.cleanUpPackageLocked("abc", USER_0, USER_0); // No changes. assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user0.getPackages().keySet())); + set(user0.getAllPackages().keySet())); assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getPackages().keySet())); + set(user10.getAllPackages().keySet())); assertEquals( makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), @@ -3357,12 +3439,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.saveDirtyInfo(); // Remove a package. + uninstallPackage(USER_0, CALLING_PACKAGE_1); mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0); assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getPackages().keySet())); + set(user0.getAllPackages().keySet())); assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getPackages().keySet())); + set(user10.getAllPackages().keySet())); assertEquals( makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), @@ -3387,12 +3470,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.saveDirtyInfo(); // Remove a launcher. + uninstallPackage(USER_10, LAUNCHER_1); mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10); assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getPackages().keySet())); + set(user0.getAllPackages().keySet())); assertEquals(makeSet(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - set(user10.getPackages().keySet())); + set(user10.getAllPackages().keySet())); assertEquals( makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), @@ -3414,12 +3498,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.saveDirtyInfo(); // Remove a package. + uninstallPackage(USER_10, CALLING_PACKAGE_2); mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10); assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getPackages().keySet())); + set(user0.getAllPackages().keySet())); assertEquals(makeSet(CALLING_PACKAGE_1), - set(user10.getPackages().keySet())); + set(user10.getAllPackages().keySet())); assertEquals( makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), @@ -3441,12 +3526,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.saveDirtyInfo(); // Remove the other launcher from user 10 too. + uninstallPackage(USER_10, LAUNCHER_2); mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10); assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getPackages().keySet())); + set(user0.getAllPackages().keySet())); assertEquals(makeSet(CALLING_PACKAGE_1), - set(user10.getPackages().keySet())); + set(user10.getAllPackages().keySet())); assertEquals( makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), @@ -3468,12 +3554,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.saveDirtyInfo(); // More remove. + uninstallPackage(USER_10, CALLING_PACKAGE_1); mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10); assertEquals(makeSet(CALLING_PACKAGE_2), - set(user0.getPackages().keySet())); + set(user0.getAllPackages().keySet())); assertEquals(makeSet(), - set(user10.getPackages().keySet())); + set(user10.getAllPackages().keySet())); assertEquals( makeSet(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), @@ -3494,97 +3581,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.saveDirtyInfo(); } - - public void testSaveAndLoadUser_forBackup() { - // Create some shortcuts. - runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - assertTrue(mManager.setDynamicShortcuts(list( - makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); - }); - runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { - assertTrue(mManager.setDynamicShortcuts(list( - makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); - }); - runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { - assertTrue(mManager.setDynamicShortcuts(list( - makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); - }); - runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { - assertTrue(mManager.setDynamicShortcuts(list( - makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); - }); - - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0)); - - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0)); - - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0)); - - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - - // Pin some. - - runWithCaller(LAUNCHER_1, USER_0, () -> { - mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, - list("s1"), HANDLE_USER_0); - - mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, - list("s2"), UserHandle.of(USER_P0)); - - mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, - list("s3"), HANDLE_USER_0); - }); - - runWithCaller(LAUNCHER_1, USER_P0, () -> { - mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, - list("s2"), HANDLE_USER_0); - - mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, - list("s3"), UserHandle.of(USER_P0)); - - mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, - list("s1"), HANDLE_USER_0); - }); - - runWithCaller(LAUNCHER_1, USER_10, () -> { - mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, - list("s3"), HANDLE_USER_10); - }); - - // Check the state. - - assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0)); - assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0)); - - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0)); - assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0)); - assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0)); - - assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0)); - assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0)); - - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10)); - assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10)); - assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); - - // Make sure all the information is persisted. - mService.saveDirtyInfo(); - initService(); - mService.handleUnlockUser(USER_0); - mService.handleUnlockUser(USER_P0); - mService.handleUnlockUser(USER_10); - } - public void testHandleGonePackage_crossProfile() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { @@ -3841,13 +3837,9 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10)); } - // TODO Detailed test for hasShortcutPermissionInner(). - - // TODO Add tests for the command line functions too. - private void checkCanRestoreTo(boolean expected, ShortcutPackageInfo spi, int version, String... signatures) { - assertEquals(expected, spi.canRestoreTo(genPackage( + assertEquals(expected, spi.canRestoreTo(mService, genPackage( "dummy", /* uid */ 0, version, signatures))); } @@ -3919,6 +3911,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10)); assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); + uninstallPackage(USER_0, CALLING_PACKAGE_1); mService.mPackageMonitor.onReceive(getTestContext(), genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0)); @@ -3929,6 +3922,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10)); assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); + uninstallPackage(USER_10, CALLING_PACKAGE_2); mService.mPackageMonitor.onReceive(getTestContext(), genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10)); @@ -3961,7 +3955,1163 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10)); } - public void testHandlePackageUpdate() { - // TODO: Make sure unshadow is called. + private void backupAndRestore() { + int prevUid = mInjectedCallingUid; + + mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it. + + dumpsysOnLogcat("Before backup"); + + final byte[] payload = mService.getBackupPayload(USER_0); + if (ENABLE_DUMP) { + final String xml = new String(payload); + Log.i(TAG, "Backup payload:"); + for (String line : xml.split("\n")) { + Log.i(TAG, line); + } + } + + // Before doing anything else, uninstall all packages. + for (int userId : list(USER_0, USER_P0)) { + for (String pkg : list(CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3, + LAUNCHER_1, LAUNCHER_2, LAUNCHER_3)) { + uninstallPackage(userId, pkg); + } + } + + initService(); + mService.applyRestore(payload, USER_0); + + // handleUnlockUser will perform the gone package check, but it shouldn't remove + // shadow information. + mService.handleUnlockUser(USER_0); + + dumpsysOnLogcat("After restore"); + + mInjectedCallingUid = prevUid; + } + + private void prepareCrossProfileDataSet() { + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), + makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), + makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), + makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list())); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), + makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"), + makeShortcut("x4"), makeShortcut("x5"), makeShortcut("x6")))); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s1", "s2", "s3"), HANDLE_USER_0); + + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s4"), HANDLE_USER_P0); + }); + runWithCaller(LAUNCHER_2, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s2", "s3"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s2", "s3", "s4"), HANDLE_USER_0); + + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s5"), HANDLE_USER_P0); + }); + runWithCaller(LAUNCHER_3, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5"), HANDLE_USER_0); + + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s6"), HANDLE_USER_P0); + }); + runWithCaller(LAUNCHER_4, USER_0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list(), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_4, list(), HANDLE_USER_0); + }); + + // Launcher on a managed profile is referring ot user 0! + runWithCaller(LAUNCHER_1, USER_P0, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), HANDLE_USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5", "s6"), + HANDLE_USER_0); + + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s4", "s1"), HANDLE_USER_P0); + }); + runWithCaller(LAUNCHER_1, USER_10, () -> { + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("x4", "x5"), HANDLE_USER_10); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("x4", "x5", "x6"), HANDLE_USER_10); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("x4", "x5", "x6", "x1"), + HANDLE_USER_10); + }); + } + + private void prepareForBackupTest() { + + prepareCrossProfileDataSet(); + + backupAndRestore(); + } + + private void assertExistsAndShadow(ShortcutPackageItem spi) { + assertNotNull(spi); + assertTrue(spi.getPackageInfo().isShadow()); + } + + /** + * Make sure the backup data doesn't have the following information: + * - Launchers on other users. + * - Non-backup app information. + * + * But restores all other infomation. + * + * It also omits the following pieces of information, but that's tested in + * {@link #testShortcutInfoSaveAndLoad_forBackup}. + * - Unpinned dynamic shortcuts + * - Bitmaps + */ + public void testBackupAndRestore() { + prepareForBackupTest(); + + checkBackupAndRestore_success(); + } + + public void testBackupAndRestore_backupRestoreTwice() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + dumpsysOnLogcat("Before second backup"); + + backupAndRestore(); + + dumpsysOnLogcat("After second backup"); + + checkBackupAndRestore_success(); + } + + public void testBackupAndRestore_backupRestoreMultiple() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + // This also shouldn't affect the result. + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"), + makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6")))); + }); + + backupAndRestore(); + + checkBackupAndRestore_success(); + } + + public void testBackupAndRestore_restoreToNewVersion() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2); + addPackage(LAUNCHER_1, LAUNCHER_UID_1, 5); + + checkBackupAndRestore_success(); + } + + public void testBackupAndRestore_restoreToSuperSetSignatures() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1, "sigx", CALLING_PACKAGE_1); + addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4, LAUNCHER_1, "sigy"); + + checkBackupAndRestore_success(); + } + + private void checkBackupAndRestore_success() { + // Make sure non-system user is not restored. + final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0); + assertEquals(0, userP0.getAllPackages().size()); + assertEquals(0, userP0.getAllLaunchers().size()); + + // Make sure only "allowBackup" apps are restored, and are shadow. + final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0); + assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_1)); + assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_2)); + assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_1))); + assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_2))); + + assertNull(user0.getAllPackages().get(CALLING_PACKAGE_3)); + assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_3))); + assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_P0, LAUNCHER_1))); + + installPackage(USER_0, CALLING_PACKAGE_1); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s1", "s2"); + }); + + installPackage(USER_0, LAUNCHER_1); + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)), + "s1"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)) + /* empty, not restored */ ); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty, not restored */ ); + + assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size()); + }); + + installPackage(USER_0, CALLING_PACKAGE_2); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s1", "s2", "s3"); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)), + "s1"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s1", "s2"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty, not restored */ ); + + assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size()); + }); + + // 3 shouldn't be backed up, so no pinned shortcuts. + installPackage(USER_0, CALLING_PACKAGE_3); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getPinnedShortcuts().size()); + }); + + // Launcher on a different profile shouldn't be restored. + runWithCaller(LAUNCHER_1, USER_P0, () -> { + assertEquals(0, + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0) + .size()); + assertEquals(0, + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0) + .size()); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* wasn't restored, so still empty */ ); + }); + + // Package on a different profile, no restore. + installPackage(USER_P0, CALLING_PACKAGE_1); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getPinnedShortcuts().size()); + }); + + // Restore launcher 2 on user 0. + installPackage(USER_0, LAUNCHER_2); + runWithCaller(LAUNCHER_2, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)), + "s2"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s2", "s3"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* wasn't restored, so still empty */ ); + + assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size()); + }); + + + // Restoration of launcher2 shouldn't affect other packages; so do the same checks and + // make sure they still have the same result. + installPackage(USER_0, CALLING_PACKAGE_1); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s1", "s2"); + }); + + installPackage(USER_0, LAUNCHER_1); + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)), + "s1"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s1", "s2"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* wasn't restored, so still empty */ ); + + assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size()); + }); + + installPackage(USER_0, CALLING_PACKAGE_2); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s1", "s2", "s3"); + }); + } + + public void testBackupAndRestore_publisherLowerVersion() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version + + checkBackupAndRestore_publisherNotRestored(); + } + + public void testBackupAndRestore_publisherWrongSignature() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature + + checkBackupAndRestore_publisherNotRestored(); + } + + public void testBackupAndRestore_publisherNoLongerBackupTarget() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + updatePackageInfo(CALLING_PACKAGE_1, + pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); + + checkBackupAndRestore_publisherNotRestored(); + } + + private void checkBackupAndRestore_publisherNotRestored() { + installPackage(USER_0, CALLING_PACKAGE_1); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getPinnedShortcuts().size()); + }); + + installPackage(USER_0, CALLING_PACKAGE_2); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s1", "s2", "s3"); + }); + + installPackage(USER_0, LAUNCHER_1); + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s1", "s2"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + installPackage(USER_0, LAUNCHER_2); + runWithCaller(LAUNCHER_2, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s2", "s3"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + + installPackage(USER_0, CALLING_PACKAGE_3); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getPinnedShortcuts().size()); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s1", "s2"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + runWithCaller(LAUNCHER_2, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s2", "s3"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + } + + public void testBackupAndRestore_launcherLowerVersion() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version + + checkBackupAndRestore_launcherNotRestored(); + } + + public void testBackupAndRestore_launcherWrongSignature() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature + + checkBackupAndRestore_launcherNotRestored(); + } + + public void testBackupAndRestore_launcherNoLongerBackupTarget() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + updatePackageInfo(LAUNCHER_1, + pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); + + checkBackupAndRestore_launcherNotRestored(); + } + + private void checkBackupAndRestore_launcherNotRestored() { + installPackage(USER_0, CALLING_PACKAGE_1); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + + // s1 was pinned by launcher 1, which is not restored, yet, so we still see "s1" here. + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s1", "s2"); + }); + + installPackage(USER_0, CALLING_PACKAGE_2); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s1", "s2", "s3"); + }); + + // Now we try to restore launcher 1. Then we realize it's not restorable, so L1 has no pinned + // shortcuts. + installPackage(USER_0, LAUNCHER_1); + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + + // Now CALLING_PACKAGE_1 realizes "s1" is no longer pinned. + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s2"); + }); + + installPackage(USER_0, LAUNCHER_2); + runWithCaller(LAUNCHER_2, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)), + "s2"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s2", "s3"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + + installPackage(USER_0, CALLING_PACKAGE_3); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getPinnedShortcuts().size()); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + runWithCaller(LAUNCHER_2, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)), + "s2"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s2", "s3"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + } + + public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() { + prepareForBackupTest(); + + // Note doing a backup & restore again here shouldn't affect the result. + backupAndRestore(); + + updatePackageInfo(CALLING_PACKAGE_1, + pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); + + updatePackageInfo(LAUNCHER_1, + pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP); + + checkBackupAndRestore_publisherAndLauncherNotRestored(); + } + + private void checkBackupAndRestore_publisherAndLauncherNotRestored() { + installPackage(USER_0, CALLING_PACKAGE_1); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getPinnedShortcuts().size()); + }); + + installPackage(USER_0, CALLING_PACKAGE_2); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s1", "s2", "s3"); + }); + + installPackage(USER_0, LAUNCHER_1); + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + installPackage(USER_0, LAUNCHER_2); + runWithCaller(LAUNCHER_2, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s2", "s3"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + + // Because launcher 1 wasn't restored, "s1" is no longer pinned. + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertShortcutIds(assertAllPinned( + mManager.getPinnedShortcuts()), + "s2", "s3"); + }); + + installPackage(USER_0, CALLING_PACKAGE_3); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getPinnedShortcuts().size()); + }); + + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + runWithCaller(LAUNCHER_2, USER_0, () -> { + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)) + /* empty */); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)), + "s2", "s3"); + assertShortcutIds(assertAllPinned( + mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) + /* empty */); + }); + } + + public void testSaveAndLoad_crossProfile() { + prepareCrossProfileDataSet(); + + dumpsysOnLogcat("Before save & load"); + + mService.saveDirtyInfo(); + initService(); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), + "s1", "s2", "s3", "s4", "s5", "s6"); + assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), + "s1", "s2", "s3", "s4"); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), + "s1", "s2", "s3", "s4", "s5", "s6"); + assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), + "s1", "s2", "s3", "s4", "s5"); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), + "s1", "s2", "s3", "s4", "s5", "s6"); + assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), + "s1", "s2", "s3", "s4", "s5", "s6"); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()) + /* empty */); + assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()) + /* empty */); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), + "s1", "s2", "s3", "s4", "s5", "s6"); + assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), + "s1", "s2", "s3", "s4", "s5", "s6"); + }); + runWithCaller(CALLING_PACKAGE_2, USER_P0, () -> { + assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()) + /* empty */); + assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()) + /* empty */); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), + "x1", "x2", "x3", "x4", "x5", "x6"); + assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), + "x4", "x5"); + }); + runWithCaller(LAUNCHER_1, USER_0, () -> { + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0), + "s1"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0), + "s1", "s2"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0), + "s1", "s2", "s3"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0), + "s1", "s4"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0) + /* empty */); + TestUtils.assertExpectException( + SecurityException.class, "", () -> { + mLauncherApps.getShortcuts( + buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10); + }); + }); + runWithCaller(LAUNCHER_2, USER_0, () -> { + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0), + "s2"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0), + "s2", "s3"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0), + "s2", "s3", "s4"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0), + "s2", "s5"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0) + /* empty */); + }); + runWithCaller(LAUNCHER_3, USER_0, () -> { + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0), + "s3"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0), + "s3", "s4"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0), + "s3", "s4", "s5"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0), + "s3", "s6"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0) + /* empty */); + }); + runWithCaller(LAUNCHER_4, USER_0, () -> { + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0) + /* empty */); + }); + runWithCaller(LAUNCHER_1, USER_P0, () -> { + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0), + "s3", "s4"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0), + "s3", "s4", "s5"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0), + "s3", "s4", "s5", "s6"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0), + "s1", "s4"); + TestUtils.assertExpectException( + SecurityException.class, "you need to be SYSTEM", () -> { + mLauncherApps.getShortcuts( + buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10); + }); + }); + runWithCaller(LAUNCHER_1, USER_10, () -> { + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_10), + "x4", "x5"); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_10) + /* empty */); + assertShortcutIds( + mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_10) + /* empty */); + TestUtils.assertExpectException( + SecurityException.class, "you need to be SYSTEM", () -> { + mLauncherApps.getShortcuts( + buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0); + }); + TestUtils.assertExpectException( + SecurityException.class, "you need to be SYSTEM", () -> { + mLauncherApps.getShortcuts( + buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0); + }); + }); + } + + // ShortcutInfo tests + + public void testShortcutInfoMissingMandatoryFields() { + TestUtils.assertExpectException( + IllegalArgumentException.class, + "ID must be provided", + () -> new ShortcutInfo.Builder(getTestContext()).build()); + TestUtils.assertExpectException( + IllegalArgumentException.class, + "title must be provided", + () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build() + .enforceMandatoryFields()); + TestUtils.assertExpectException( + NullPointerException.class, + "Intent must be provided", + () -> new ShortcutInfo.Builder(getTestContext()).setId("id").setTitle("x").build() + .enforceMandatoryFields()); + } + + public void testShortcutInfoParcel() { + ShortcutInfo si = parceled(new ShortcutInfo.Builder(getTestContext()) + .setId("id") + .setTitle("title") + .setIntent(makeIntent("action", ShortcutActivity.class)) + .build()); + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals("title", si.getTitle()); + assertEquals("action", si.getIntent().getAction()); + + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + + si = new ShortcutInfo.Builder(getTestContext()) + .setId("id") + .setActivityComponent(new ComponentName("a", "b")) + .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setTitle("title") + .setText("text") + .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setWeight(123) + .setExtras(pb) + .build(); + si.addFlags(ShortcutInfo.FLAG_PINNED); + si.setBitmapPath("abc"); + si.setIconResourceId(456); + + si = parceled(si); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); + assertEquals("content://a.b.c/", si.getIcon().getUriString()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals("abc", si.getBitmapPath()); + assertEquals(456, si.getIconResourceId()); + } + + public void testShortcutInfoClone() { + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext()) + .setId("id") + .setActivityComponent(new ComponentName("a", "b")) + .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setTitle("title") + .setText("text") + .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setWeight(123) + .setExtras(pb) + .build(); + sorig.addFlags(ShortcutInfo.FLAG_PINNED); + sorig.setBitmapPath("abc"); + sorig.setIconResourceId(456); + + ShortcutInfo si = sorig.clone(/* clone flags*/ 0); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); + assertEquals("content://a.b.c/", si.getIcon().getUriString()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals("abc", si.getBitmapPath()); + assertEquals(456, si.getIconResourceId()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); + assertEquals(null, si.getIcon()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals(null, si.getBitmapPath()); + assertEquals(0, si.getIconResourceId()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); + assertEquals(null, si.getIcon()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals(null, si.getIntent()); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals(null, si.getBitmapPath()); + assertEquals(0, si.getIconResourceId()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); + + assertEquals(getTestContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(null, si.getActivityComponent()); + assertEquals(null, si.getIcon()); + assertEquals(null, si.getTitle()); + assertEquals(null, si.getText()); + assertEquals(null, si.getIntent()); + assertEquals(0, si.getWeight()); + assertEquals(null, si.getExtras()); + + assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags()); + assertEquals(null, si.getBitmapPath()); + assertEquals(0, si.getIconResourceId()); + } + + public void testShortcutInfoCopyNonNullFieldsFrom() throws InterruptedException { + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext()) + .setId("id") + .setActivityComponent(new ComponentName("a", "b")) + .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setTitle("title") + .setText("text") + .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setWeight(123) + .setExtras(pb) + .build(); + sorig.addFlags(ShortcutInfo.FLAG_PINNED); + sorig.setBitmapPath("abc"); + sorig.setIconResourceId(456); + + ShortcutInfo si; + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setActivityComponent(new ComponentName("x", "y")).build()); + assertEquals(new ComponentName("x", "y"), si.getActivityComponent()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setIcon(Icon.createWithContentUri("content://x.y.z/")).build()); + assertEquals("content://x.y.z/", si.getIcon().getUriString()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setTitle("xyz").build()); + assertEquals("xyz", si.getTitle()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setText("xxx").build()); + assertEquals("xxx", si.getText()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setIntent(makeIntent("action2", ShortcutActivity.class)).build()); + assertEquals("action2", si.getIntent().getAction()); + assertEquals(null, si.getIntent().getStringExtra("key")); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build()); + assertEquals("action3", si.getIntent().getAction()); + assertEquals("x", si.getIntent().getStringExtra("key")); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setWeight(999).build()); + assertEquals(999, si.getWeight()); + + + PersistableBundle pb2 = new PersistableBundle(); + pb2.putInt("x", 99); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setExtras(pb2).build()); + assertEquals(99, si.getExtras().getInt("x")); + + final long timestamp = si.getLastChangedTimestamp(); + Thread.sleep(2); + + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") + .setTitle("xyz").build()); + + assertTrue(si.getLastChangedTimestamp() > timestamp); + } + + public void testShortcutInfoSaveAndLoad() throws InterruptedException { + setCaller(CALLING_PACKAGE_1, USER_0); + + final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.black_32x32)); + + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext) + .setId("id") + .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class)) + .setIcon(bmp32x32) + .setTitle("title") + .setText("text") + .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setWeight(123) + .setExtras(pb) + .build(); + + mManager.addDynamicShortcut(sorig); + + Thread.sleep(2); + final long now = System.currentTimeMillis(); + + // Save and load. + mService.saveDirtyInfo(); + initService(); + mService.handleUnlockUser(USER_0); + + ShortcutInfo si; + si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0); + + assertEquals(CALLING_PACKAGE_1, si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName()); + assertEquals(null, si.getIcon()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE, si.getFlags()); + assertNotNull(si.getBitmapPath()); // Something should be set. + assertEquals(0, si.getIconResourceId()); + assertTrue(si.getLastChangedTimestamp() < now); + } + + public void testShortcutInfoSaveAndLoad_forBackup() { + setCaller(CALLING_PACKAGE_1, USER_0); + + final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.black_32x32)); + + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext) + .setId("id") + .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class)) + .setIcon(bmp32x32) + .setTitle("title") + .setText("text") + .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setWeight(123) + .setExtras(pb) + .build(); + + mManager.addDynamicShortcut(sorig); + + // Dynamic shortcuts won't be backed up, so we need to pin it. + setCaller(LAUNCHER_1, USER_0); + mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id"), HANDLE_USER_0); + + // Do backup & restore. + backupAndRestore(); + + ShortcutInfo si; + si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0); + + assertEquals(CALLING_PACKAGE_1, si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName()); + assertEquals(null, si.getIcon()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertNull(si.getBitmapPath()); // No icon. + assertEquals(0, si.getIconResourceId()); + } + + public void testDumpsys_crossProfile() { + prepareCrossProfileDataSet(); + dumpsysOnLogcat("test1", /* force= */ true); + } + + public void testDumpsys_withIcons() { + testIcons(); + // Dump after having some icons. + dumpsysOnLogcat("test1", /* force= */ true); } } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index b56ce736aef6..dbc2b0c9f850 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -170,6 +170,15 @@ public final class PhoneAccount implements Parcelable { public static final int CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE = 0x100; /** + * Flag indicating that for this {@link PhoneAccount}, emergency video calling is allowed. + * <p> + * When set, Telecom will allow emergency video calls to be placed. When not set, Telecom will + * convert all outgoing video calls to emergency numbers to audio-only. + * @hide + */ + public static final int CAPABILITY_EMERGENCY_VIDEO_CALLING = 0x200; + + /** * URI scheme for telephone number URIs. */ public static final String SCHEME_TEL = "tel"; @@ -731,6 +740,9 @@ public final class PhoneAccount implements Parcelable { if (hasCapabilities(CAPABILITY_PLACE_EMERGENCY_CALLS)) { sb.append("PlaceEmerg "); } + if (hasCapabilities(CAPABILITY_EMERGENCY_VIDEO_CALLING)) { + sb.append("EmergVideo "); + } if (hasCapabilities(CAPABILITY_SIM_SUBSCRIPTION)) { sb.append("SimSub "); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index c69a360aeb5e..461611d0114c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -252,6 +252,16 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool"; /** + * Flag specifying whether WFC over IMS supports the "wifi only" option. If false, the wifi + * calling settings will not include an option for "wifi only". If true, the wifi calling + * settings will include an option for "wifi only" + * <p> + * By default, it is assumed that WFC supports "wifi only". + */ + public static final String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = + "carrier_wfc_supports_wifi_only_bool"; + + /** * Default WFC_IMS_mode 0: WIFI_ONLY * 1: CELLULAR_PREFERRED * 2: WIFI_PREFERRED @@ -628,6 +638,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false); sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2); diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index ed7351f85a7a..033312b76edd 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -62,7 +62,7 @@ public class AppLaunch extends InstrumentationTestCase { private static final String KEY_REQUIRED_ACCOUNTS = "required_accounts"; private static final String WEARABLE_ACTION_GOOGLE = "com.google.android.wearable.action.GOOGLE"; - private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle + private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 60000; //60s to allow app to idle private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java index ad02d2b77a99..c0583ceb2fab 100644 --- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java +++ b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java @@ -18,6 +18,7 @@ package android.hardware.soundtrigger; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -40,6 +41,7 @@ import java.io.DataOutputStream; import java.net.InetAddress; import java.net.Socket; import java.util.ArrayList; +import java.util.HashSet; import java.util.Random; import java.util.UUID; @@ -53,7 +55,7 @@ public class GenericSoundModelTest extends AndroidTestCase { static final int MSG_GENERIC_TRIGGER = 4; private Random random = new Random(); - private ArrayList<UUID> loadedModelUuids; + private HashSet<UUID> loadedModelUuids; private ISoundTriggerService soundTriggerService; private SoundTriggerManager soundTriggerManager; @@ -68,7 +70,7 @@ public class GenericSoundModelTest extends AndroidTestCase { soundTriggerManager = (SoundTriggerManager) context.getSystemService( Context.SOUND_TRIGGER_SERVICE); - loadedModelUuids = new ArrayList<UUID>(); + loadedModelUuids = new HashSet<UUID>(); } @Override @@ -170,6 +172,101 @@ public class GenericSoundModelTest extends AndroidTestCase { verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any()); } + /** + * Tests a more complicated pattern of loading, unloading, triggering, starting and stopping + * recognition. Intended to find unexpected errors that occur in unexpected states. + */ + @LargeTest + public void testFuzzGenericSoundModel() throws Exception { + int numModels = 2; + + final int STATUS_UNLOADED = 0; + final int STATUS_LOADED = 1; + final int STATUS_STARTED = 2; + + class ModelInfo { + int status; + GenericSoundModel model; + + public ModelInfo(GenericSoundModel model, int status) { + this.status = status; + this.model = model; + } + } + + Random predictableRandom = new Random(100); + + ArrayList modelInfos = new ArrayList<ModelInfo>(); + for(int i=0; i<numModels; i++) { + // Create sound model + byte[] data = new byte[1024]; + predictableRandom.nextBytes(data); + UUID modelUuid = UUID.randomUUID(); + UUID mVendorUuid = UUID.randomUUID(); + GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data); + ModelInfo modelInfo = new ModelInfo(model, STATUS_UNLOADED); + modelInfos.add(modelInfo); + } + + boolean captureTriggerAudio = true; + boolean allowMultipleTriggers = true; + RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, + null, null); + TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); + + + int numOperationsToRun = 100; + for(int i=0; i<numOperationsToRun; i++) { + // Select a random model + int modelInfoIndex = predictableRandom.nextInt(modelInfos.size()); + ModelInfo modelInfo = (ModelInfo) modelInfos.get(modelInfoIndex); + + // Perform a random operation + int operation = predictableRandom.nextInt(5); + + if (operation == 0 && modelInfo.status == STATUS_UNLOADED) { + // Update and start sound model + soundTriggerService.updateSoundModel(modelInfo.model); + loadedModelUuids.add(modelInfo.model.uuid); + modelInfo.status = STATUS_LOADED; + } else if (operation == 1 && modelInfo.status == STATUS_LOADED) { + // Start the sound model + int r = soundTriggerService.startRecognition(new ParcelUuid(modelInfo.model.uuid), + spyCallback, config); + assertEquals("Could Not Start Recognition with code: " + r, + android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); + modelInfo.status = STATUS_STARTED; + } else if (operation == 2 && modelInfo.status == STATUS_STARTED) { + // Send trigger to stub HAL + Socket socket = new Socket(InetAddress.getLocalHost(), 14035); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + out.writeBytes("trig " + modelInfo.model.uuid + "\r\n"); + out.flush(); + socket.close(); + + // Verify trigger was received + verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any()); + reset(spyCallback); + } else if (operation == 3 && modelInfo.status == STATUS_STARTED) { + // Stop recognition + int r = soundTriggerService.stopRecognition(new ParcelUuid(modelInfo.model.uuid), + spyCallback); + assertEquals("Could Not Stop Recognition with code: " + r, + android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); + modelInfo.status = STATUS_LOADED; + } else if (operation == 4 && modelInfo.status != STATUS_UNLOADED) { + // Delete sound model + soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.uuid)); + loadedModelUuids.remove(modelInfo.model.uuid); + + // Confirm it was deleted + GenericSoundModel returnedModel = + soundTriggerService.getSoundModel(new ParcelUuid(modelInfo.model.uuid)); + assertEquals(null, returnedModel); + modelInfo.status = STATUS_UNLOADED; + } + } + } public class TestRecognitionStatusCallback extends IRecognitionStatusCallback.Stub { @Override diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 7faee1b3d174..f9e008e1cf92 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -403,8 +403,15 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void setNewConfiguration(Configuration arg0) throws RemoteException { + public int[] setNewConfiguration(Configuration arg0) throws RemoteException { // TODO Auto-generated method stub + return null; + } + + @Override + public Rect getBoundsForNewConfiguration(int stackId) throws RemoteException { + // TODO Auto-generated method stub + return null; } @Override |