diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-11-17 16:38:15 -0500 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-11-17 16:38:15 -0500 |
commit | 6a65f2da209bff03cb0eb6da309710ac6ee5026d (patch) | |
tree | 48e2090e716d4178378cb0599fc5d9cffbcf3f63 /android/app | |
parent | 46c77c203439b3b37c99d09e326df4b1fe08c10b (diff) | |
download | android-28-6a65f2da209bff03cb0eb6da309710ac6ee5026d.tar.gz |
Import Android SDK Platform P [4456821]
/google/data/ro/projects/android/fetch_artifact \
--bid 4456821 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4456821.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: I2d206b200d7952f899a5d1647ab532638cc8dd43
Diffstat (limited to 'android/app')
36 files changed, 934 insertions, 387 deletions
diff --git a/android/app/Activity.java b/android/app/Activity.java index 9d331a02..99f3dee7 100644 --- a/android/app/Activity.java +++ b/android/app/Activity.java @@ -16,8 +16,6 @@ package android.app; -import static android.os.Build.VERSION_CODES.O_MR1; - import static java.lang.Character.MIN_VALUE; import android.annotation.CallSuper; @@ -136,6 +134,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; + /** * An activity is a single, focused thing that the user can do. Almost all * activities interact with the user, so the Activity class takes care of @@ -194,10 +193,13 @@ import java.util.List; * <a name="Fragments"></a> * <h3>Fragments</h3> * - * <p>Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, Activity - * implementations can make use of the {@link Fragment} class to better + * <p>The {@link android.support.v4.app.FragmentActivity} subclass + * can make use of the {@link android.support.v4.app.Fragment} class to better * modularize their code, build more sophisticated user interfaces for larger - * screens, and help scale their application between small and large screens. + * screens, and help scale their application between small and large screens.</p> + * + * <p>For more information about using fragments, read the + * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p> * * <a name="ActivityLifecycle"></a> * <h3>Activity Lifecycle</h3> @@ -916,7 +918,10 @@ public class Activity extends ContextThemeWrapper /** * Return the LoaderManager for this activity, creating it if needed. + * + * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportLoaderManager()} */ + @Deprecated public LoaderManager getLoaderManager() { return mFragments.getLoaderManager(); } @@ -991,17 +996,6 @@ public class Activity extends ContextThemeWrapper protected void onCreate(@Nullable Bundle savedInstanceState) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); - if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) { - final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window); - final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta); - ta.recycle(); - - if (isTranslucentOrFloating) { - throw new IllegalStateException( - "Only fullscreen opaque activities can request orientation"); - } - } - if (mLastNonConfigurationInstances != null) { mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders); } @@ -2407,7 +2401,10 @@ public class Activity extends ContextThemeWrapper /** * Return the FragmentManager for interacting with fragments associated * with this activity. + * + * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} */ + @Deprecated public FragmentManager getFragmentManager() { return mFragments.getFragmentManager(); } @@ -2416,7 +2413,11 @@ public class Activity extends ContextThemeWrapper * Called when a Fragment is being attached to this activity, immediately * after the call to its {@link Fragment#onAttach Fragment.onAttach()} * method and before {@link Fragment#onCreate Fragment.onCreate()}. + * + * @deprecated Use {@link + * android.support.v4.app.FragmentActivity#onAttachFragment(android.support.v4.app.Fragment)} */ + @Deprecated public void onAttachFragment(Fragment fragment) { } @@ -5118,7 +5119,11 @@ public class Activity extends ContextThemeWrapper * * @see Fragment#startActivity * @see Fragment#startActivityForResult + * + * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment( + * android.support.v4.app.Fragment,Intent,int)} */ + @Deprecated public void startActivityFromFragment(@NonNull Fragment fragment, @RequiresPermission Intent intent, int requestCode) { startActivityFromFragment(fragment, intent, requestCode, null); @@ -5143,7 +5148,11 @@ public class Activity extends ContextThemeWrapper * * @see Fragment#startActivity * @see Fragment#startActivityForResult + * + * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment( + * android.support.v4.app.Fragment,Intent,int,Bundle)} */ + @Deprecated public void startActivityFromFragment(@NonNull Fragment fragment, @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { startActivityForResult(fragment.mWho, intent, requestCode, options); @@ -7304,24 +7313,25 @@ public class Activity extends ContextThemeWrapper } /** - * Request to put this Activity in a mode where the user is locked to the - * current task. + * Request to put this activity in a mode where the user is locked to a restricted set of + * applications. * - * This will prevent the user from launching other apps, going to settings, or reaching the - * home screen. This does not include those apps whose {@link android.R.attr#lockTaskMode} - * values permit launching while locked. + * <p>If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns {@code true} + * for this component, the current task will be launched directly into LockTask mode. Only apps + * whitelisted by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])} can + * be launched while LockTask mode is active. The user will not be able to leave this mode + * until this activity calls {@link #stopLockTask()}. Calling this method while the device is + * already in LockTask mode has no effect. * - * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true or - * lockTaskMode=lockTaskModeAlways for this component then the app will go directly into - * Lock Task mode. The user will not be able to exit this mode until - * {@link Activity#stopLockTask()} is called. + * <p>Otherwise, the current task will be launched into screen pinning mode. In this case, the + * system will prompt the user with a dialog requesting permission to use this mode. + * The user can exit at any time through instructions shown on the request dialog. Calling + * {@link #stopLockTask()} will also terminate this mode. * - * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false - * then the system will prompt the user with a dialog requesting permission to enter - * this mode. When entered through this method the user can exit at any time through - * an action described by the request dialog. Calling stopLockTask will also exit the - * mode. + * <p><strong>Note:</strong> this method can only be called when the activity is foreground. + * That is, between {@link #onResume()} and {@link #onPause()}. * + * @see #stopLockTask() * @see android.R.attr#lockTaskMode */ public void startLockTask() { @@ -7332,25 +7342,24 @@ public class Activity extends ContextThemeWrapper } /** - * Allow the user to switch away from the current task. + * Stop the current task from being locked. * - * Called to end the mode started by {@link Activity#startLockTask}. This - * can only be called by activities that have successfully called - * startLockTask previously. + * <p>Called to end the LockTask or screen pinning mode started by {@link #startLockTask()}. + * This can only be called by activities that have called {@link #startLockTask()} previously. * - * This will allow the user to exit this app and move onto other activities. - * <p>Note: This method should only be called when the activity is user-facing. That is, - * between onResume() and onPause(). - * <p>Note: If there are other tasks below this one that are also locked then calling this - * method will immediately finish this task and resume the previous locked one, remaining in - * lockTask mode. + * <p><strong>Note:</strong> If the device is in LockTask mode that is not initially started + * by this activity, then calling this method will not terminate the LockTask mode, but only + * finish its own task. The device will remain in LockTask mode, until the activity which + * started the LockTask mode calls this method, or until its whitelist authorization is revoked + * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}. * + * @see #startLockTask() * @see android.R.attr#lockTaskMode * @see ActivityManager#getLockTaskModeState() */ public void stopLockTask() { try { - ActivityManager.getService().stopLockTaskMode(); + ActivityManager.getService().stopLockTaskModeByToken(mToken); } catch (RemoteException e) { } } diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java index 8d9dc1fa..064e9782 100644 --- a/android/app/ActivityManager.java +++ b/android/app/ActivityManager.java @@ -682,20 +682,23 @@ public class ActivityManager { } /** - * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which - * specifies the position of the created docked stack at the top half of the screen if + * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary} + * which specifies the position of the created docked stack at the top half of the screen if * in portrait mode or at the left half of the screen if in landscape mode. * @hide */ - public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0; + @TestApi + public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0; /** - * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which + * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary} + * which * specifies the position of the created docked stack at the bottom half of the screen if * in portrait mode or at the right half of the screen if in landscape mode. * @hide */ - public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1; + @TestApi + public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1; /** * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates @@ -1925,6 +1928,33 @@ public class ActivityManager { } /** + * Moves the input task to the primary-split-screen stack. + * @param taskId Id of task to move. + * @param createMode The mode the primary split screen stack should be created in if it doesn't + * exist already. See + * {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT} + * and + * {@link android.app.ActivityManager + * #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT} + * @param toTop If the task and stack should be moved to the top. + * @param animate Whether we should play an animation for the moving the task + * @param initialBounds If the primary stack gets created, it will use these bounds for the + * docked stack. Pass {@code null} to use default bounds. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop, + boolean animate, Rect initialBounds) throws SecurityException { + try { + getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate, + initialBounds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Resizes the input stack id to the given bounds. * @param stackId Id of the stack to resize. * @param bounds Bounds to resize the stack to or {@code null} for fullscreen. diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java index 9d14f616..a46b3c72 100644 --- a/android/app/ActivityManagerInternal.java +++ b/android/app/ActivityManagerInternal.java @@ -64,6 +64,27 @@ public abstract class ActivityManagerInternal { public static final int APP_TRANSITION_SNAPSHOT = 4; /** + * The bundle key to extract the assist data. + */ + public static final String ASSIST_KEY_DATA = "data"; + + /** + * The bundle key to extract the assist structure. + */ + public static final String ASSIST_KEY_STRUCTURE = "structure"; + + /** + * The bundle key to extract the assist content. + */ + public static final String ASSIST_KEY_CONTENT = "content"; + + /** + * The bundle key to extract the assist receiver extras. + */ + public static final String ASSIST_KEY_RECEIVER_EXTRAS = "receiverExtras"; + + + /** * Grant Uri permissions from one app to another. This method only extends * permission grants if {@code callingUid} has permission to them. */ diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java index b62e4c2d..4a21f5c4 100644 --- a/android/app/ActivityOptions.java +++ b/android/app/ActivityOptions.java @@ -16,7 +16,7 @@ package android.app; -import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.INVALID_DISPLAY; @@ -203,10 +203,11 @@ public class ActivityOptions { "android.activity.taskOverlayCanResume"; /** - * Where the docked stack should be positioned. + * Where the split-screen-primary stack should be positioned. * @hide */ - private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode"; + private static final String KEY_SPLIT_SCREEN_CREATE_MODE = + "android:activity.splitScreenCreateMode"; /** * Determines whether to disallow the outgoing activity from entering picture-in-picture as the @@ -292,7 +293,7 @@ public class ActivityOptions { @WindowConfiguration.ActivityType private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED; private int mLaunchTaskId = -1; - private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; + private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; private boolean mDisallowEnterPictureInPictureWhileLaunching; private boolean mTaskOverlay; private boolean mTaskOverlayCanResume; @@ -884,7 +885,8 @@ public class ActivityOptions { mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1); mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false); mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false); - mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT); + mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE, + SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean( KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false); if (opts.containsKey(KEY_ANIM_SPECS)) { @@ -1194,13 +1196,13 @@ public class ActivityOptions { } /** @hide */ - public int getDockCreateMode() { - return mDockCreateMode; + public int getSplitScreenCreateMode() { + return mSplitScreenCreateMode; } /** @hide */ - public void setDockCreateMode(int dockCreateMode) { - mDockCreateMode = dockCreateMode; + public void setSplitScreenCreateMode(int splitScreenCreateMode) { + mSplitScreenCreateMode = splitScreenCreateMode; } /** @hide */ @@ -1369,7 +1371,7 @@ public class ActivityOptions { b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId); b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay); b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume); - b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode); + b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode); b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, mDisallowEnterPictureInPictureWhileLaunching); if (mAnimSpecs != null) { diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java index 2516a3e9..21e454f1 100644 --- a/android/app/ActivityThread.java +++ b/android/app/ActivityThread.java @@ -5533,32 +5533,8 @@ public final class ActivityThread { View.mDebugViewAttributes = mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0; - /** - * For system applications on userdebug/eng builds, log stack - * traces of disk and network access to dropbox for analysis. - */ - if ((data.appInfo.flags & - (ApplicationInfo.FLAG_SYSTEM | - ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) { - StrictMode.conditionallyEnableDebugLogging(); - } - - /** - * For apps targetting Honeycomb or later, we don't allow network usage - * on the main event loop / UI thread. This is what ultimately throws - * {@link NetworkOnMainThreadException}. - */ - if (data.appInfo.targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) { - StrictMode.enableDeathOnNetwork(); - } - - /** - * For apps targetting N or later, we don't allow file:// Uri exposure. - * This is what ultimately throws {@link FileUriExposedException}. - */ - if (data.appInfo.targetSdkVersion >= Build.VERSION_CODES.N) { - StrictMode.enableDeathOnFileUriExposure(); - } + StrictMode.initThreadDefaults(data.appInfo); + StrictMode.initVmDefaults(data.appInfo); // We deprecated Build.SERIAL and only apps that target pre NMR1 // SDK can see it. Since access to the serial is now behind a @@ -5655,7 +5631,12 @@ public final class ActivityThread { mResourcesManager.getConfiguration().getLocales()); if (!Process.isIsolated()) { - setupGraphicsSupport(appContext); + final int oldMask = StrictMode.allowThreadDiskWritesMask(); + try { + setupGraphicsSupport(appContext); + } finally { + StrictMode.setThreadPolicyMask(oldMask); + } } // If we use profiles, setup the dex reporter to notify package manager diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java index 4bd85ae9..b6fb1201 100644 --- a/android/app/AppOpsManager.java +++ b/android/app/AppOpsManager.java @@ -254,8 +254,10 @@ public class AppOpsManager { public static final int OP_ANSWER_PHONE_CALLS = 69; /** @hide Run jobs when in background */ public static final int OP_RUN_ANY_IN_BACKGROUND = 70; + /** @hide Change Wi-Fi connectivity state */ + public static final int OP_CHANGE_WIFI_STATE = 71; /** @hide */ - public static final int _NUM_OP = 71; + public static final int _NUM_OP = 72; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -496,6 +498,7 @@ public class AppOpsManager { OP_INSTANT_APP_START_FOREGROUND, OP_ANSWER_PHONE_CALLS, OP_RUN_ANY_IN_BACKGROUND, + OP_CHANGE_WIFI_STATE, }; /** @@ -574,6 +577,7 @@ public class AppOpsManager { OPSTR_INSTANT_APP_START_FOREGROUND, OPSTR_ANSWER_PHONE_CALLS, null, // OP_RUN_ANY_IN_BACKGROUND + null, // OP_CHANGE_WIFI_STATE }; /** @@ -652,6 +656,7 @@ public class AppOpsManager { "INSTANT_APP_START_FOREGROUND", "ANSWER_PHONE_CALLS", "RUN_ANY_IN_BACKGROUND", + "CHANGE_WIFI_STATE", }; /** @@ -730,6 +735,7 @@ public class AppOpsManager { Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE, Manifest.permission.ANSWER_PHONE_CALLS, null, // no permission for OP_RUN_ANY_IN_BACKGROUND + Manifest.permission.CHANGE_WIFI_STATE, }; /** @@ -809,6 +815,7 @@ public class AppOpsManager { null, // INSTANT_APP_START_FOREGROUND null, // ANSWER_PHONE_CALLS null, // OP_RUN_ANY_IN_BACKGROUND + null, // OP_CHANGE_WIFI_STATE }; /** @@ -887,6 +894,7 @@ public class AppOpsManager { false, // INSTANT_APP_START_FOREGROUND false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND + false, // OP_CHANGE_WIFI_STATE }; /** @@ -964,6 +972,7 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND + AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE }; /** @@ -1045,6 +1054,7 @@ public class AppOpsManager { false, false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND + false, // OP_CHANGE_WIFI_STATE }; /** diff --git a/android/app/DexLoadReporter.java b/android/app/DexLoadReporter.java index f99d1a8e..06434147 100644 --- a/android/app/DexLoadReporter.java +++ b/android/app/DexLoadReporter.java @@ -19,7 +19,6 @@ package android.app; import android.os.FileUtils; import android.os.RemoteException; import android.os.SystemProperties; -import android.system.ErrnoException; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -27,8 +26,6 @@ import com.android.internal.annotations.GuardedBy; import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; -import libcore.io.Libcore; - import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -155,23 +152,12 @@ import java.util.Set; return; } - File realDexPath; - try { - // Secondary dex profiles are stored in the oat directory, next to the real dex file - // and have the same name with 'cur.prof' appended. We use the realpath because that - // is what installd is using when processing the dex file. - // NOTE: Keep in sync with installd. - realDexPath = new File(Libcore.os.realpath(dexPath)); - } catch (ErrnoException ex) { - Slog.e(TAG, "Failed to get the real path of secondary dex " + dexPath - + ":" + ex.getMessage()); - // Do not continue with registration if we could not retrieve the real path. - return; - } - + // Secondary dex profiles are stored in the oat directory, next to dex file + // and have the same name with 'cur.prof' appended. // NOTE: Keep this in sync with installd expectations. - File secondaryProfileDir = new File(realDexPath.getParent(), "oat"); - File secondaryProfile = new File(secondaryProfileDir, realDexPath.getName() + ".cur.prof"); + File dexPathFile = new File(dexPath); + File secondaryProfileDir = new File(dexPathFile.getParent(), "oat"); + File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof"); // Create the profile if not already there. // Returns true if the file was created, false if the file already exists. diff --git a/android/app/DialogFragment.java b/android/app/DialogFragment.java index 7e0e4d82..a0fb6eeb 100644 --- a/android/app/DialogFragment.java +++ b/android/app/DialogFragment.java @@ -136,7 +136,10 @@ import java.io.PrintWriter; * * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java * embed} + * + * @deprecated Use {@link android.support.v4.app.DialogFragment} */ +@Deprecated public class DialogFragment extends Fragment implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { diff --git a/android/app/Fragment.java b/android/app/Fragment.java index 93773454..a92684b5 100644 --- a/android/app/Fragment.java +++ b/android/app/Fragment.java @@ -256,7 +256,10 @@ import java.lang.reflect.InvocationTargetException; * <p>After each call to this function, a new entry is on the stack, and * pressing back will pop it to return the user to whatever previous state * the activity UI was in. + * + * @deprecated Use {@link android.support.v4.app.Fragment} */ +@Deprecated public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener { private static final ArrayMap<String, Class<?>> sClassMap = new ArrayMap<String, Class<?>>(); @@ -414,7 +417,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * State information that has been retrieved from a fragment instance * through {@link FragmentManager#saveFragmentInstanceState(Fragment) * FragmentManager.saveFragmentInstanceState}. + * + * @deprecated Use {@link android.support.v4.app.Fragment.SavedState} */ + @Deprecated public static class SavedState implements Parcelable { final Bundle mState; @@ -458,7 +464,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene /** * Thrown by {@link Fragment#instantiate(Context, String, Bundle)} when * there is an instantiation failure. + * + * @deprecated Use {@link android.support.v4.app.Fragment.InstantiationException} */ + @Deprecated static public class InstantiationException extends AndroidRuntimeException { public InstantiationException(String msg, Exception cause) { super(msg, cause); @@ -1031,7 +1040,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene /** * Return the LoaderManager for this fragment, creating it if needed. + * + * @deprecated Use {@link android.support.v4.app.Fragment#getLoaderManager()} */ + @Deprecated public LoaderManager getLoaderManager() { if (mLoaderManager != null) { return mLoaderManager; diff --git a/android/app/FragmentBreadCrumbs.java b/android/app/FragmentBreadCrumbs.java index d0aa0fd6..e3e47ae6 100644 --- a/android/app/FragmentBreadCrumbs.java +++ b/android/app/FragmentBreadCrumbs.java @@ -65,7 +65,10 @@ public class FragmentBreadCrumbs extends ViewGroup /** * Interface to intercept clicks on the bread crumbs. + * + * @deprecated This widget is no longer supported. */ + @Deprecated public interface OnBreadCrumbClickListener { /** * Called when a bread crumb is clicked. diff --git a/android/app/FragmentContainer.java b/android/app/FragmentContainer.java index f8836bc8..a1dd32ff 100644 --- a/android/app/FragmentContainer.java +++ b/android/app/FragmentContainer.java @@ -24,7 +24,10 @@ import android.view.View; /** * Callbacks to a {@link Fragment}'s container. + * + * @deprecated Use {@link android.support.v4.app.FragmentContainer} */ +@Deprecated public abstract class FragmentContainer { /** * Return the view with the given resource ID. May return {@code null} if the diff --git a/android/app/FragmentController.java b/android/app/FragmentController.java index cff94d8c..cbb58d40 100644 --- a/android/app/FragmentController.java +++ b/android/app/FragmentController.java @@ -37,7 +37,10 @@ import java.util.List; * <p> * It is the responsibility of the host to take care of the Fragment's lifecycle. * The methods provided by {@link FragmentController} are for that purpose. + * + * @deprecated Use {@link android.support.v4.app.FragmentController} */ +@Deprecated public class FragmentController { private final FragmentHostCallback<?> mHost; diff --git a/android/app/FragmentHostCallback.java b/android/app/FragmentHostCallback.java index 5ef23e63..1edc68ed 100644 --- a/android/app/FragmentHostCallback.java +++ b/android/app/FragmentHostCallback.java @@ -37,7 +37,10 @@ import java.io.PrintWriter; * Fragments may be hosted by any object; such as an {@link Activity}. In order to * host fragments, implement {@link FragmentHostCallback}, overriding the methods * applicable to the host. + * + * @deprecated Use {@link android.support.v4.app.FragmentHostCallback} */ +@Deprecated public abstract class FragmentHostCallback<E> extends FragmentContainer { private final Activity mActivity; final Context mContext; diff --git a/android/app/FragmentManager.java b/android/app/FragmentManager.java index 0d5cd021..12e60b87 100644 --- a/android/app/FragmentManager.java +++ b/android/app/FragmentManager.java @@ -74,7 +74,10 @@ import java.util.concurrent.CopyOnWriteArrayList; * {@link android.support.v4.app.FragmentActivity}. See the blog post * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html"> * Fragments For All</a> for more details. + * + * @deprecated Use {@link android.support.v4.app.FragmentManager} */ +@Deprecated public abstract class FragmentManager { /** * Representation of an entry on the fragment back stack, as created @@ -86,7 +89,10 @@ public abstract class FragmentManager { * <p>Note that you should never hold on to a BackStackEntry object; * the identifier as returned by {@link #getId} is the only thing that * will be persisted across activity instances. + * + * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry} */ + @Deprecated public interface BackStackEntry { /** * Return the unique identifier for the entry. This is the only @@ -129,7 +135,10 @@ public abstract class FragmentManager { /** * Interface to watch for changes to the back stack. + * + * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener} */ + @Deprecated public interface OnBackStackChangedListener { /** * Called whenever the contents of the back stack change. @@ -428,7 +437,10 @@ public abstract class FragmentManager { /** * Callback interface for listening to fragment state changes that happen * within a given FragmentManager. + * + * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks} */ + @Deprecated public abstract static class FragmentLifecycleCallbacks { /** * Called right before the fragment's {@link Fragment#onAttach(Context)} method is called. diff --git a/android/app/FragmentManagerNonConfig.java b/android/app/FragmentManagerNonConfig.java index 50d3797d..beb1a15a 100644 --- a/android/app/FragmentManagerNonConfig.java +++ b/android/app/FragmentManagerNonConfig.java @@ -27,7 +27,10 @@ import java.util.List; * and passed to the state save and restore process for fragments in * {@link FragmentController#retainNonConfig()} and * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p> + * + * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig} */ +@Deprecated public class FragmentManagerNonConfig { private final List<Fragment> mFragments; private final List<FragmentManagerNonConfig> mChildNonConfigs; diff --git a/android/app/FragmentTransaction.java b/android/app/FragmentTransaction.java index c910e903..0f4a7fb5 100644 --- a/android/app/FragmentTransaction.java +++ b/android/app/FragmentTransaction.java @@ -21,7 +21,10 @@ import java.lang.annotation.RetentionPolicy; * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer * guide.</p> * </div> + * + * @deprecated Use {@link android.support.v4.app.FragmentTransaction} */ +@Deprecated public abstract class FragmentTransaction { /** * Calls {@link #add(int, Fragment, String)} with a 0 containerViewId. diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java index e260967f..d49e11f4 100644 --- a/android/app/Instrumentation.java +++ b/android/app/Instrumentation.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; @@ -418,22 +419,51 @@ public class Instrumentation { * different process. In addition, if the given Intent resolves to * multiple activities, instead of displaying a dialog for the user to * select an activity, an exception will be thrown. - * + * * <p>The function returns as soon as the activity goes idle following the * call to its {@link Activity#onCreate}. Generally this means it has gone * through the full initialization including {@link Activity#onResume} and * drawn and displayed its initial window. - * + * * @param intent Description of the activity to start. - * + * * @see Context#startActivity + * @see #startActivitySync(Intent, Bundle) */ public Activity startActivitySync(Intent intent) { + return startActivitySync(intent, null /* options */); + } + + /** + * Start a new activity and wait for it to begin running before returning. + * In addition to being synchronous, this method as some semantic + * differences from the standard {@link Context#startActivity} call: the + * activity component is resolved before talking with the activity manager + * (its class name is specified in the Intent that this method ultimately + * starts), and it does not allow you to start activities that run in a + * different process. In addition, if the given Intent resolves to + * multiple activities, instead of displaying a dialog for the user to + * select an activity, an exception will be thrown. + * + * <p>The function returns as soon as the activity goes idle following the + * call to its {@link Activity#onCreate}. Generally this means it has gone + * through the full initialization including {@link Activity#onResume} and + * drawn and displayed its initial window. + * + * @param intent Description of the activity to start. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. See {@link android.app.ActivityOptions} + * for how to build the Bundle supplied here; there are no supported definitions + * for building it manually. + * + * @see Context#startActivity(Intent, Bundle) + */ + public Activity startActivitySync(Intent intent, @Nullable Bundle options) { validateNotAppThread(); synchronized (mSync) { intent = new Intent(intent); - + ActivityInfo ai = intent.resolveActivityInfo( getTargetContext().getPackageManager(), 0); if (ai == null) { @@ -447,7 +477,7 @@ public class Instrumentation { + myProc + " resolved to different process " + ai.processName + ": " + intent); } - + intent.setComponent(new ComponentName( ai.applicationInfo.packageName, ai.name)); final ActivityWaiter aw = new ActivityWaiter(intent); @@ -457,7 +487,7 @@ public class Instrumentation { } mWaitingActivities.add(aw); - getTargetContext().startActivity(intent); + getTargetContext().startActivity(intent, options); do { try { @@ -465,7 +495,7 @@ public class Instrumentation { } catch (InterruptedException e) { } } while (mWaitingActivities.contains(aw)); - + return aw.activity; } } diff --git a/android/app/ListFragment.java b/android/app/ListFragment.java index 0b96d84d..90b77b39 100644 --- a/android/app/ListFragment.java +++ b/android/app/ListFragment.java @@ -144,7 +144,10 @@ import android.widget.TextView; * * @see #setListAdapter * @see android.widget.ListView + * + * @deprecated Use {@link android.support.v4.app.ListFragment} */ +@Deprecated public class ListFragment extends Fragment { final private Handler mHandler = new Handler(); diff --git a/android/app/LoaderManager.java b/android/app/LoaderManager.java index 56dfc589..7969684a 100644 --- a/android/app/LoaderManager.java +++ b/android/app/LoaderManager.java @@ -54,11 +54,17 @@ import java.lang.reflect.Modifier; * <p>For more information about using loaders, read the * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p> * </div> + * + * @deprecated Use {@link android.support.v4.app.LoaderManager} */ +@Deprecated public abstract class LoaderManager { /** * Callback interface for a client to interact with the manager. + * + * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks} */ + @Deprecated public interface LoaderCallbacks<D> { /** * Instantiate and return a new Loader for the given ID. diff --git a/android/app/Notification.java b/android/app/Notification.java index 8226e0fb..d5d95fb8 100644 --- a/android/app/Notification.java +++ b/android/app/Notification.java @@ -22,6 +22,7 @@ import android.annotation.ColorInt; import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -3900,7 +3901,7 @@ public class Notification implements Parcelable final Bundle ex = mN.extras; updateBackgroundColor(contentView); bindNotificationHeader(contentView, p.ambient); - bindLargeIcon(contentView); + bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply); boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex); if (p.title != null) { contentView.setViewVisibility(R.id.title, View.VISIBLE); @@ -4110,11 +4111,13 @@ public class Notification implements Parcelable } } - private void bindLargeIcon(RemoteViews contentView) { + private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon, + boolean alwaysShowReply) { if (mN.mLargeIcon == null && mN.largeIcon != null) { mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); } - if (mN.mLargeIcon != null) { + boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon; + if (showLargeIcon) { contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon); processLargeLegacyIcon(mN.mLargeIcon, contentView); @@ -4122,32 +4125,45 @@ public class Notification implements Parcelable contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin); contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin); contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin); - // Bind the reply action - Action action = findReplyAction(); - contentView.setViewVisibility(R.id.reply_icon_action, action != null - ? View.VISIBLE - : View.GONE); - - if (action != null) { - int contrastColor = resolveContrastColor(); + } + // Bind the reply action + Action action = findReplyAction(); + + boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply); + int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon; + if (actionVisible) { + // We're only showing the icon as big if we're hiding the large icon + int contrastColor = resolveContrastColor(); + int iconColor; + if (showLargeIcon) { contentView.setDrawableTint(R.id.reply_icon_action, true /* targetBackground */, contrastColor, PorterDuff.Mode.SRC_ATOP); - int iconColor = NotificationColorUtil.isColorLight(contrastColor) - ? Color.BLACK : Color.WHITE; - contentView.setDrawableTint(R.id.reply_icon_action, - false /* targetBackground */, - iconColor, PorterDuff.Mode.SRC_ATOP); contentView.setOnClickPendingIntent(R.id.right_icon, action.actionIntent); - contentView.setOnClickPendingIntent(R.id.reply_icon_action, - action.actionIntent); contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs); - contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs); - + iconColor = NotificationColorUtil.isColorLight(contrastColor) + ? Color.BLACK : Color.WHITE; + } else { + contentView.setImageViewResource(R.id.right_icon, + R.drawable.ic_reply_notification_large); + contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); + iconColor = contrastColor; } + contentView.setDrawableTint(replyId, + false /* targetBackground */, + iconColor, + PorterDuff.Mode.SRC_ATOP); + contentView.setOnClickPendingIntent(replyId, + action.actionIntent); + contentView.setRemoteInputs(replyId, action.mRemoteInputs); + } else { + contentView.setRemoteInputs(R.id.right_icon, null); } - contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null + contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon + ? View.VISIBLE + : View.GONE); + contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon ? View.VISIBLE : View.GONE); } @@ -6055,18 +6071,12 @@ public class Notification implements Parcelable protected void restoreFromExtras(Bundle extras) { super.restoreFromExtras(extras); - mMessages.clear(); - mHistoricMessages.clear(); mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); - if (messages != null && messages instanceof Parcelable[]) { - mMessages = Message.getMessagesFromBundleArray(messages); - } + mMessages = Message.getMessagesFromBundleArray(messages); Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); - if (histMessages != null && histMessages instanceof Parcelable[]) { - mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); - } + mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); } /** @@ -6074,38 +6084,34 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeContentView(boolean increasedHeight) { - if (!increasedHeight) { - Message m = findLatestIncomingMessage(); - CharSequence title = mConversationTitle != null - ? mConversationTitle - : (m == null) ? null : m.mSender; - CharSequence text = (m == null) - ? null - : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText; - - return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(), - mBuilder.mParams.reset().hasProgress(false).title(title).text(text)); - } else { - mBuilder.mOriginalActions = mBuilder.mActions; - mBuilder.mActions = new ArrayList<>(); - RemoteViews remoteViews = makeBigContentView(); - mBuilder.mActions = mBuilder.mOriginalActions; - mBuilder.mOriginalActions = null; - return remoteViews; - } + mBuilder.mOriginalActions = mBuilder.mActions; + mBuilder.mActions = new ArrayList<>(); + RemoteViews remoteViews = makeBigContentView(); + mBuilder.mActions = mBuilder.mOriginalActions; + mBuilder.mOriginalActions = null; + return remoteViews; } private Message findLatestIncomingMessage() { - for (int i = mMessages.size() - 1; i >= 0; i--) { - Message m = mMessages.get(i); + return findLatestIncomingMessage(mMessages); + } + + /** + * @hide + */ + @Nullable + public static Message findLatestIncomingMessage( + List<Message> messages) { + for (int i = messages.size() - 1; i >= 0; i--) { + Message m = messages.get(i); // Incoming messages have a non-empty sender. if (!TextUtils.isEmpty(m.mSender)) { return m; } } - if (!mMessages.isEmpty()) { + if (!messages.isEmpty()) { // No incoming messages, fall back to outgoing message - return mMessages.get(mMessages.size() - 1); + return messages.get(messages.size() - 1); } return null; } @@ -6115,118 +6121,82 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeBigContentView() { - CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle) + CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; - boolean hasTitle = !TextUtils.isEmpty(title); - - if (mMessages.size() == 1) { - // Special case for a single message: Use the big text style - // so the collapsed and expanded versions match nicely. - CharSequence bigTitle; - CharSequence text; - if (hasTitle) { - bigTitle = title; - text = makeMessageLine(mMessages.get(0), mBuilder); - } else { - bigTitle = mMessages.get(0).mSender; - text = mMessages.get(0).mText; - } - RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( - mBuilder.getBigTextLayoutResource(), - mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null)); - BigTextStyle.applyBigTextContentView(mBuilder, contentView, text); - return contentView; - } - + boolean isOneToOne = TextUtils.isEmpty(conversationTitle); + if (isOneToOne) { + // Let's add the conversationTitle in case we didn't have one before and all + // messages are from the same sender + conversationTitle = createConversationTitleFromMessages(); + } else if (hasOnlyWhiteSpaceSenders()) { + isOneToOne = true; + } + boolean hasTitle = !TextUtils.isEmpty(conversationTitle); RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getMessagingLayoutResource(), - mBuilder.mParams.reset().hasProgress(false).title(title).text(null)); - - int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, - R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; - - // Make sure all rows are gone in case we reuse a view. - for (int rowId : rowIds) { - contentView.setViewVisibility(rowId, View.GONE); - } + mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null) + .hideLargeIcon(isOneToOne).alwaysShowReply(true)); + addExtras(mBuilder.mN.extras); + contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", + mBuilder.resolveContrastColor()); + contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", + mBuilder.mN.mLargeIcon); + contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", + isOneToOne); + contentView.setBundle(R.id.status_bar_latest_event_content, "setData", + mBuilder.mN.extras); + return contentView; + } - int i=0; - contentView.setViewLayoutMarginBottomDimen(R.id.line1, - hasTitle ? R.dimen.notification_messaging_spacing : 0); - contentView.setInt(R.id.notification_messaging, "setNumIndentLines", - !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2)); - - int contractedChildId = View.NO_ID; - Message contractedMessage = findLatestIncomingMessage(); - int firstHistoricMessage = Math.max(0, mHistoricMessages.size() - - (rowIds.length - mMessages.size())); - while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) { - Message m = mHistoricMessages.get(firstHistoricMessage + i); - int rowId = rowIds[i]; - - contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder)); - - if (contractedMessage == m) { - contractedChildId = rowId; + private boolean hasOnlyWhiteSpaceSenders() { + for (int i = 0; i < mMessages.size(); i++) { + Message m = mMessages.get(i); + CharSequence sender = m.getSender(); + if (!isWhiteSpace(sender)) { + return false; } - - i++; } + return true; + } - int firstMessage = Math.max(0, mMessages.size() - rowIds.length); - while (firstMessage + i < mMessages.size() && i < rowIds.length) { - Message m = mMessages.get(firstMessage + i); - int rowId = rowIds[i]; - - contentView.setViewVisibility(rowId, View.VISIBLE); - contentView.setTextViewText(rowId, mBuilder.processTextSpans( - makeMessageLine(m, mBuilder))); - mBuilder.setTextViewColorSecondary(contentView, rowId); - - if (contractedMessage == m) { - contractedChildId = rowId; - } - - i++; + private boolean isWhiteSpace(CharSequence sender) { + if (TextUtils.isEmpty(sender)) { + return true; } - // Clear the remaining views for reapply. Ensures that historic message views can - // reliably be identified as being GONE and having non-null text. - while (i < rowIds.length) { - int rowId = rowIds[i]; - contentView.setTextViewText(rowId, null); - i++; + if (sender.toString().matches("^\\s*$")) { + return true; } - - // Record this here to allow transformation between the contracted and expanded views. - contentView.setInt(R.id.notification_messaging, "setContractedChildId", - contractedChildId); - return contentView; + // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround + // For the presentation that we had. + for (int i = 0; i < sender.length(); i++) { + char c = sender.charAt(i); + if (c != '\u200B') { + return false; + } + } + return true; } - private CharSequence makeMessageLine(Message m, Builder builder) { - BidiFormatter bidi = BidiFormatter.getInstance(); - SpannableStringBuilder sb = new SpannableStringBuilder(); - boolean colorize = builder.isColorized(); - TextAppearanceSpan colorSpan; - CharSequence messageName; - if (TextUtils.isEmpty(m.mSender)) { - CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName; - sb.append(bidi.unicodeWrap(replyName), - makeFontColorSpan(colorize - ? builder.getPrimaryTextColor() - : mBuilder.resolveContrastColor()), - 0 /* flags */); - } else { - sb.append(bidi.unicodeWrap(m.mSender), - makeFontColorSpan(colorize - ? builder.getPrimaryTextColor() - : Color.BLACK), - 0 /* flags */); + private CharSequence createConversationTitleFromMessages() { + ArraySet<CharSequence> names = new ArraySet<>(); + for (int i = 0; i < mMessages.size(); i++) { + Message m = mMessages.get(i); + CharSequence sender = m.getSender(); + if (sender != null) { + names.add(sender); + } + } + SpannableStringBuilder title = new SpannableStringBuilder(); + int size = names.size(); + for (int i = 0; i < size; i++) { + CharSequence name = names.valueAt(i); + if (!TextUtils.isEmpty(title)) { + title.append(", "); + } + title.append(BidiFormatter.getInstance().unicodeWrap(name)); } - CharSequence text = m.mText == null ? "" : m.mText; - sb.append(" ").append(bidi.unicodeWrap(text)); - return sb; + return title; } /** @@ -6234,19 +6204,9 @@ public class Notification implements Parcelable */ @Override public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { - if (increasedHeight) { - return makeBigContentView(); - } - Message m = findLatestIncomingMessage(); - CharSequence title = mConversationTitle != null - ? mConversationTitle - : (m == null) ? null : m.mSender; - CharSequence text = (m == null) - ? null - : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText; - - return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(), - mBuilder.mParams.reset().hasProgress(false).title(title).text(text)); + RemoteViews remoteViews = makeBigContentView(); + remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); + return remoteViews; } private static TextAppearanceSpan makeFontColorSpan(int color) { @@ -6394,7 +6354,15 @@ public class Notification implements Parcelable return bundles; } - static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) { + /** + * @return A list of messages read from the bundles. + * + * @hide + */ + public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) { + if (bundles == null) { + return new ArrayList<>(); + } List<Message> messages = new ArrayList<>(bundles.length); for (int i = 0; i < bundles.length; i++) { if (bundles[i] instanceof Bundle) { @@ -8487,6 +8455,8 @@ public class Notification implements Parcelable boolean ambient = false; CharSequence title; CharSequence text; + boolean hideLargeIcon; + public boolean alwaysShowReply; final StandardTemplateParams reset() { hasProgress = true; @@ -8511,6 +8481,16 @@ public class Notification implements Parcelable return this; } + final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) { + this.alwaysShowReply = alwaysShowReply; + return this; + } + + final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) { + this.hideLargeIcon = hideLargeIcon; + return this; + } + final StandardTemplateParams ambient(boolean ambient) { Preconditions.checkState(title == null && text == null, "must set ambient before text"); this.ambient = ambient; @@ -8527,7 +8507,6 @@ public class Notification implements Parcelable text = extras.getCharSequence(EXTRA_TEXT); } this.text = b.processLegacyText(text, ambient); - return this; } } diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java index a52dc1e4..f931589b 100644 --- a/android/app/NotificationManager.java +++ b/android/app/NotificationManager.java @@ -758,10 +758,10 @@ public class NotificationManager { } /** - * Checks the ability to read/modify notification do not disturb policy for the calling package. + * Checks the ability to modify notification do not disturb policy for the calling package. * * <p> - * Returns true if the calling package can read/modify notification policy. + * Returns true if the calling package can modify notification policy. * * <p> * Apps can request policy access by sending the user to the activity that matches the system @@ -839,8 +839,6 @@ public class NotificationManager { * Gets the current notification policy. * * <p> - * Only available if policy access is granted to this package. - * See {@link #isNotificationPolicyAccessGranted}. */ public Policy getNotificationPolicy() { INotificationManager service = getService(); diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java index 50f1f364..e48946f2 100644 --- a/android/app/SystemServiceRegistry.java +++ b/android/app/SystemServiceRegistry.java @@ -41,6 +41,8 @@ import android.content.pm.IShortcutService; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; +import android.content.pm.crossprofile.CrossProfileApps; +import android.content.pm.crossprofile.ICrossProfileApps; import android.content.res.Resources; import android.hardware.ConsumerIrManager; import android.hardware.ISerialManager; @@ -81,6 +83,7 @@ import android.net.INetworkPolicyManager; import android.net.IpSecManager; import android.net.NetworkPolicyManager; import android.net.NetworkScoreManager; +import android.net.NetworkWatchlistManager; import android.net.lowpan.ILowpanManager; import android.net.lowpan.LowpanManager; import android.net.nsd.INsdManager; @@ -134,6 +137,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccManager; import android.util.Log; +import android.util.StatsManager; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.WindowManager; @@ -150,6 +154,7 @@ import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.internal.app.ISoundTriggerService; import com.android.internal.appwidget.IAppWidgetService; +import com.android.internal.net.INetworkWatchlistManager; import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; @@ -304,14 +309,14 @@ final class SystemServiceRegistry { }}); registerService(Context.BATTERY_SERVICE, BatteryManager.class, - new StaticServiceFetcher<BatteryManager>() { + new CachedServiceFetcher<BatteryManager>() { @Override - public BatteryManager createService() throws ServiceNotFoundException { + public BatteryManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBatteryStats stats = IBatteryStats.Stub.asInterface( ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME)); IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub .asInterface(ServiceManager.getServiceOrThrow("batteryproperties")); - return new BatteryManager(stats, registrar); + return new BatteryManager(ctx, stats, registrar); }}); registerService(Context.NFC_SERVICE, NfcManager.class, @@ -448,6 +453,13 @@ final class SystemServiceRegistry { ctx.mMainThread.getHandler().getLooper()); }}); + registerService(Context.STATS_MANAGER, StatsManager.class, + new StaticServiceFetcher<StatsManager>() { + @Override + public StatsManager createService() throws ServiceNotFoundException { + return new StatsManager(); + }}); + registerService(Context.STATUS_BAR_SERVICE, StatusBarManager.class, new CachedServiceFetcher<StatusBarManager>() { @Override @@ -862,6 +874,17 @@ final class SystemServiceRegistry { return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b)); }}); + registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class, + new CachedServiceFetcher<NetworkWatchlistManager>() { + @Override + public NetworkWatchlistManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = + ServiceManager.getServiceOrThrow(Context.NETWORK_WATCHLIST_SERVICE); + return new NetworkWatchlistManager(ctx, + INetworkWatchlistManager.Stub.asInterface(b)); + }}); + registerService(Context.SYSTEM_HEALTH_SERVICE, SystemHealthManager.class, new CachedServiceFetcher<SystemHealthManager>() { @Override @@ -909,6 +932,18 @@ final class SystemServiceRegistry { public RulesManager createService(ContextImpl ctx) { return new RulesManager(ctx.getOuterContext()); }}); + + registerService(Context.CROSS_PROFILE_APPS_SERVICE, CrossProfileApps.class, + new CachedServiceFetcher<CrossProfileApps>() { + @Override + public CrossProfileApps createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.CROSS_PROFILE_APPS_SERVICE); + return new CrossProfileApps(ctx.getOuterContext(), + ICrossProfileApps.Stub.asInterface(b)); + } + }); } /** diff --git a/android/app/TimePickerDialog.java b/android/app/TimePickerDialog.java index 0f006b66..8686944b 100644 --- a/android/app/TimePickerDialog.java +++ b/android/app/TimePickerDialog.java @@ -152,6 +152,9 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, public void onClick(View view) { if (mTimePicker.validateInput()) { TimePickerDialog.this.onClick(TimePickerDialog.this, BUTTON_POSITIVE); + // Clearing focus forces the dialog to commit any pending + // changes, e.g. typed text in a NumberPicker. + mTimePicker.clearFocus(); dismiss(); } } diff --git a/android/app/VrManager.java b/android/app/VrManager.java index 5c6ffa39..392387a9 100644 --- a/android/app/VrManager.java +++ b/android/app/VrManager.java @@ -198,4 +198,20 @@ public class VrManager { e.rethrowFromSystemServer(); } } + + /** + * Sets the current standby status of the VR device. Standby mode is only used on standalone vr + * devices. Standby mode is a deep sleep state where it's appropriate to turn off vr mode. + * + * @param standby True if the device is entering standby, false if it's exiting standby. + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_VR_MANAGER) + public void setStandbyEnabled(boolean standby) { + try { + mService.setStandbyEnabled(standby); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } } diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java index de27b4fd..2c1fad1c 100644 --- a/android/app/WindowConfiguration.java +++ b/android/app/WindowConfiguration.java @@ -500,15 +500,12 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu * @hide */ public boolean supportSplitScreenWindowingMode() { - return supportSplitScreenWindowingMode(mWindowingMode, mActivityType); + return supportSplitScreenWindowingMode(mActivityType); } /** @hide */ - public static boolean supportSplitScreenWindowingMode(int windowingMode, int activityType) { - if (activityType == ACTIVITY_TYPE_ASSISTANT) { - return false; - } - return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED; + public static boolean supportSplitScreenWindowingMode(int activityType) { + return activityType != ACTIVITY_TYPE_ASSISTANT; } /** @hide */ diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java index 772c6d60..f0226b7e 100644 --- a/android/app/admin/DevicePolicyManager.java +++ b/android/app/admin/DevicePolicyManager.java @@ -3246,6 +3246,7 @@ public class DevicePolicyManager { * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} */ public void wipeData(int flags) { + throwIfParentInstance("wipeData"); final String wipeReasonForUser = mContext.getString( R.string.work_profile_deleted_description_dpm_wipe); wipeDataInternal(flags, wipeReasonForUser); @@ -3270,6 +3271,7 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if the input reason string is null or empty. */ public void wipeDataWithReason(int flags, @NonNull CharSequence reason) { + throwIfParentInstance("wipeDataWithReason"); Preconditions.checkNotNull(reason, "CharSequence is null"); wipeDataInternal(flags, reason.toString()); } @@ -3283,7 +3285,6 @@ public class DevicePolicyManager { * @hide */ private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) { - throwIfParentInstance("wipeDataWithReason"); if (mService != null) { try { mService.wipeDataWithReason(flags, wipeReasonForUser); @@ -6096,8 +6097,8 @@ public class DevicePolicyManager { /** * Flag used by {@link #createAndManageUser} to specify that the user should be created - * ephemeral. - * @hide + * ephemeral. Ephemeral users will be removed after switching to another user or rebooting the + * device. */ public static final int MAKE_USER_EPHEMERAL = 0x0002; diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java index b640bd5b..530d84b4 100644 --- a/android/app/job/JobInfo.java +++ b/android/app/job/JobInfo.java @@ -16,6 +16,12 @@ package android.app.job; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.util.TimeUtils.formatDuration; import android.annotation.BytesLong; @@ -25,6 +31,8 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.content.ClipData; import android.content.ComponentName; +import android.net.NetworkRequest; +import android.net.NetworkSpecifier; import android.net.Uri; import android.os.BaseBundle; import android.os.Bundle; @@ -56,6 +64,7 @@ public class JobInfo implements Parcelable { NETWORK_TYPE_ANY, NETWORK_TYPE_UNMETERED, NETWORK_TYPE_NOT_ROAMING, + NETWORK_TYPE_CELLULAR, NETWORK_TYPE_METERED, }) @Retention(RetentionPolicy.SOURCE) @@ -69,8 +78,21 @@ public class JobInfo implements Parcelable { public static final int NETWORK_TYPE_UNMETERED = 2; /** This job requires network connectivity that is not roaming. */ public static final int NETWORK_TYPE_NOT_ROAMING = 3; - /** This job requires metered connectivity such as most cellular data networks. */ - public static final int NETWORK_TYPE_METERED = 4; + /** This job requires network connectivity that is a cellular network. */ + public static final int NETWORK_TYPE_CELLULAR = 4; + + /** + * This job requires metered connectivity such as most cellular data + * networks. + * + * @deprecated Cellular networks may be unmetered, or Wi-Fi networks may be + * metered, so this isn't a good way of selecting a specific + * transport. Instead, use {@link #NETWORK_TYPE_CELLULAR} or + * {@link android.net.NetworkRequest.Builder#addTransportType(int)} + * if your job requires a specific network transport. + */ + @Deprecated + public static final int NETWORK_TYPE_METERED = NETWORK_TYPE_CELLULAR; /** Sentinel value indicating that bytes are unknown. */ public static final int NETWORK_BYTES_UNKNOWN = -1; @@ -253,7 +275,7 @@ public class JobInfo implements Parcelable { private final long triggerContentMaxDelay; private final boolean hasEarlyConstraint; private final boolean hasLateConstraint; - private final int networkType; + private final NetworkRequest networkRequest; private final long networkBytes; private final long minLatencyMillis; private final long maxExecutionDelayMillis; @@ -385,10 +407,37 @@ public class JobInfo implements Parcelable { } /** - * The kind of connectivity requirements that the job has. + * Return the basic description of the kind of network this job requires. + * + * @deprecated This method attempts to map {@link #getRequiredNetwork()} + * into the set of simple constants, which results in a loss of + * fidelity. Callers should move to using + * {@link #getRequiredNetwork()} directly. + * @see Builder#setRequiredNetworkType(int) */ + @Deprecated public @NetworkType int getNetworkType() { - return networkType; + if (networkRequest == null) { + return NETWORK_TYPE_NONE; + } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) { + return NETWORK_TYPE_UNMETERED; + } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING)) { + return NETWORK_TYPE_NOT_ROAMING; + } else if (networkRequest.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { + return NETWORK_TYPE_CELLULAR; + } else { + return NETWORK_TYPE_ANY; + } + } + + /** + * Return the detailed description of the kind of network this job requires, + * or {@code null} if no specific kind of network is required. + * + * @see Builder#setRequiredNetwork(NetworkRequest) + */ + public @Nullable NetworkRequest getRequiredNetwork() { + return networkRequest; } /** @@ -438,8 +487,7 @@ public class JobInfo implements Parcelable { * job does not recur periodically. */ public long getIntervalMillis() { - final long minInterval = getMinPeriodMillis(); - return intervalMillis >= minInterval ? intervalMillis : minInterval; + return intervalMillis; } /** @@ -447,10 +495,7 @@ public class JobInfo implements Parcelable { * execute at any time in a window of flex length at the end of the period. */ public long getFlexMillis() { - long interval = getIntervalMillis(); - long percentClamp = 5 * interval / 100; - long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis())); - return clampedFlex <= interval ? clampedFlex : interval; + return flexMillis; } /** @@ -459,8 +504,7 @@ public class JobInfo implements Parcelable { * to 30 seconds, minimum is currently 10 seconds. */ public long getInitialBackoffMillis() { - final long minBackoff = getMinBackoffMillis(); - return initialBackoffMillis >= minBackoff ? initialBackoffMillis : minBackoff; + return initialBackoffMillis; } /** @@ -538,7 +582,7 @@ public class JobInfo implements Parcelable { if (hasLateConstraint != j.hasLateConstraint) { return false; } - if (networkType != j.networkType) { + if (!Objects.equals(networkRequest, j.networkRequest)) { return false; } if (networkBytes != j.networkBytes) { @@ -601,7 +645,9 @@ public class JobInfo implements Parcelable { hashCode = 31 * hashCode + Long.hashCode(triggerContentMaxDelay); hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint); hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint); - hashCode = 31 * hashCode + networkType; + if (networkRequest != null) { + hashCode = 31 * hashCode + networkRequest.hashCode(); + } hashCode = 31 * hashCode + Long.hashCode(networkBytes); hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis); hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis); @@ -632,7 +678,11 @@ public class JobInfo implements Parcelable { triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR); triggerContentUpdateDelay = in.readLong(); triggerContentMaxDelay = in.readLong(); - networkType = in.readInt(); + if (in.readInt() != 0) { + networkRequest = NetworkRequest.CREATOR.createFromParcel(in); + } else { + networkRequest = null; + } networkBytes = in.readLong(); minLatencyMillis = in.readLong(); maxExecutionDelayMillis = in.readLong(); @@ -661,7 +711,7 @@ public class JobInfo implements Parcelable { : null; triggerContentUpdateDelay = b.mTriggerContentUpdateDelay; triggerContentMaxDelay = b.mTriggerContentMaxDelay; - networkType = b.mNetworkType; + networkRequest = b.mNetworkRequest; networkBytes = b.mNetworkBytes; minLatencyMillis = b.mMinLatencyMillis; maxExecutionDelayMillis = b.mMaxExecutionDelayMillis; @@ -699,7 +749,12 @@ public class JobInfo implements Parcelable { out.writeTypedArray(triggerContentUris, flags); out.writeLong(triggerContentUpdateDelay); out.writeLong(triggerContentMaxDelay); - out.writeInt(networkType); + if (networkRequest != null) { + out.writeInt(1); + networkRequest.writeToParcel(out, flags); + } else { + out.writeInt(0); + } out.writeLong(networkBytes); out.writeLong(minLatencyMillis); out.writeLong(maxExecutionDelayMillis); @@ -833,7 +888,7 @@ public class JobInfo implements Parcelable { private int mFlags; // Requirements. private int mConstraintFlags; - private int mNetworkType; + private NetworkRequest mNetworkRequest; private long mNetworkBytes = NETWORK_BYTES_UNKNOWN; private ArrayList<TriggerContentUri> mTriggerContentUris; private long mTriggerContentUpdateDelay = -1; @@ -934,24 +989,84 @@ public class JobInfo implements Parcelable { } /** - * Set some description of the kind of network type your job needs to - * have. Not calling this function means the network is not necessary, - * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that - * calling this function defines network as a strict requirement for - * your job. If the network requested is not available your job will - * never run. See {@link #setOverrideDeadline(long)} to change this - * behaviour. + * Set basic description of the kind of network your job requires. If + * you need more precise control over network capabilities, see + * {@link #setRequiredNetwork(NetworkRequest)}. + * <p> + * If your job doesn't need a network connection, you don't need to call + * this method, as the default value is {@link #NETWORK_TYPE_NONE}. + * <p> + * Calling this method defines network as a strict requirement for your + * job. If the network requested is not available your job will never + * run. See {@link #setOverrideDeadline(long)} to change this behavior. + * Calling this method will override any requirements previously defined + * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only + * want to call one of these methods. * <p class="note"> - * Note: When your job executes in + * When your job executes in * {@link JobService#onStartJob(JobParameters)}, be sure to use the * specific network returned by {@link JobParameters#getNetwork()}, * otherwise you'll use the default network which may not meet this * constraint. * + * @see #setRequiredNetwork(NetworkRequest) + * @see JobInfo#getNetworkType() * @see JobParameters#getNetwork() */ public Builder setRequiredNetworkType(@NetworkType int networkType) { - mNetworkType = networkType; + if (networkType == NETWORK_TYPE_NONE) { + return setRequiredNetwork(null); + } else { + final NetworkRequest.Builder builder = new NetworkRequest.Builder(); + + // All types require validated Internet + builder.addCapability(NET_CAPABILITY_INTERNET); + builder.addCapability(NET_CAPABILITY_VALIDATED); + builder.removeCapability(NET_CAPABILITY_NOT_VPN); + + if (networkType == NETWORK_TYPE_ANY) { + // No other capabilities + } else if (networkType == NETWORK_TYPE_UNMETERED) { + builder.addCapability(NET_CAPABILITY_NOT_METERED); + } else if (networkType == NETWORK_TYPE_NOT_ROAMING) { + builder.addCapability(NET_CAPABILITY_NOT_ROAMING); + } else if (networkType == NETWORK_TYPE_CELLULAR) { + builder.addTransportType(TRANSPORT_CELLULAR); + } + + return setRequiredNetwork(builder.build()); + } + } + + /** + * Set detailed description of the kind of network your job requires. + * <p> + * If your job doesn't need a network connection, you don't need to call + * this method, as the default is {@code null}. + * <p> + * Calling this method defines network as a strict requirement for your + * job. If the network requested is not available your job will never + * run. See {@link #setOverrideDeadline(long)} to change this behavior. + * Calling this method will override any requirements previously defined + * by {@link #setRequiredNetworkType(int)}; you typically only want to + * call one of these methods. + * <p class="note"> + * When your job executes in + * {@link JobService#onStartJob(JobParameters)}, be sure to use the + * specific network returned by {@link JobParameters#getNetwork()}, + * otherwise you'll use the default network which may not meet this + * constraint. + * + * @param networkRequest The detailed description of the kind of network + * this job requires, or {@code null} if no specific kind of + * network is required. Defining a {@link NetworkSpecifier} + * is only supported for jobs that aren't persisted. + * @see #setRequiredNetworkType(int) + * @see JobInfo#getRequiredNetwork() + * @see JobParameters#getNetwork() + */ + public Builder setRequiredNetwork(@Nullable NetworkRequest networkRequest) { + mNetworkRequest = networkRequest; return this; } @@ -1140,6 +1255,21 @@ public class JobInfo implements Parcelable { * higher. */ public Builder setPeriodic(long intervalMillis, long flexMillis) { + final long minPeriod = getMinPeriodMillis(); + if (intervalMillis < minPeriod) { + Log.w(TAG, "Requested interval " + formatDuration(intervalMillis) + " for job " + + mJobId + " is too small; raising to " + formatDuration(minPeriod)); + intervalMillis = minPeriod; + } + + final long percentClamp = 5 * intervalMillis / 100; + final long minFlex = Math.max(percentClamp, getMinFlexMillis()); + if (flexMillis < minFlex) { + Log.w(TAG, "Requested flex " + formatDuration(flexMillis) + " for job " + mJobId + + " is too small; raising to " + formatDuration(minFlex)); + flexMillis = minFlex; + } + mIsPeriodic = true; mIntervalMillis = intervalMillis; mFlexMillis = flexMillis; @@ -1189,6 +1319,13 @@ public class JobInfo implements Parcelable { */ public Builder setBackoffCriteria(long initialBackoffMillis, @BackoffPolicy int backoffPolicy) { + final long minBackoff = getMinBackoffMillis(); + if (initialBackoffMillis < minBackoff) { + Log.w(TAG, "Requested backoff " + formatDuration(initialBackoffMillis) + " for job " + + mJobId + " is too small; raising to " + formatDuration(minBackoff)); + initialBackoffMillis = minBackoff; + } + mBackoffPolicySet = true; mInitialBackoffMillis = initialBackoffMillis; mBackoffPolicy = backoffPolicy; @@ -1213,16 +1350,22 @@ public class JobInfo implements Parcelable { public JobInfo build() { // Allow jobs with no constraints - What am I, a database? if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 && - mNetworkType == NETWORK_TYPE_NONE && + mNetworkRequest == null && mTriggerContentUris == null) { throw new IllegalArgumentException("You're trying to build a job with no " + "constraints, this is not allowed."); } // Check that network estimates require network type - if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) { + if (mNetworkBytes > 0 && mNetworkRequest == null) { throw new IllegalArgumentException( "Can't provide estimated network usage without requiring a network"); } + // We can't serialize network specifiers + if (mIsPersisted && mNetworkRequest != null + && mNetworkRequest.networkCapabilities.getNetworkSpecifier() != null) { + throw new IllegalArgumentException( + "Network specifiers aren't supported for persistent jobs"); + } // Check that a deadline was not set on a periodic job. if (mIsPeriodic) { if (mMaxExecutionDelayMillis != 0L) { @@ -1257,31 +1400,7 @@ public class JobInfo implements Parcelable { " back-off policy, so calling setBackoffCriteria with" + " setRequiresDeviceIdle is an error."); } - JobInfo job = new JobInfo(this); - if (job.isPeriodic()) { - if (job.intervalMillis != job.getIntervalMillis()) { - StringBuilder builder = new StringBuilder(); - builder.append("Specified interval for ") - .append(String.valueOf(mJobId)) - .append(" is "); - formatDuration(mIntervalMillis, builder); - builder.append(". Clamped to "); - formatDuration(job.getIntervalMillis(), builder); - Log.w(TAG, builder.toString()); - } - if (job.flexMillis != job.getFlexMillis()) { - StringBuilder builder = new StringBuilder(); - builder.append("Specified flex for ") - .append(String.valueOf(mJobId)) - .append(" is "); - formatDuration(mFlexMillis, builder); - builder.append(". Clamped to "); - formatDuration(job.getFlexMillis(), builder); - Log.w(TAG, builder.toString()); - } - } - return job; + return new JobInfo(this); } } - } diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java index f6b6b869..616a5be3 100644 --- a/android/app/slice/Slice.java +++ b/android/app/slice/Slice.java @@ -21,8 +21,12 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.app.PendingIntent; import android.app.RemoteInput; +import android.content.ContentProvider; import android.content.ContentResolver; +import android.content.Context; import android.content.IContentProvider; +import android.content.Intent; +import android.content.pm.ResolveInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; @@ -54,7 +58,12 @@ public final class Slice implements Parcelable { public @interface SliceHint{ } /** - * Hint that this content is a title of other content in the slice. + * Hint that this content is a title of other content in the slice. This can also indicate that + * the content should be used in the shortcut representation of the slice (icon, label, action), + * normally this should be indicated by adding the hint on the action containing that content. + * + * @see SliceView#MODE_SHORTCUT + * @see SliceItem#TYPE_ACTION */ public static final String HINT_TITLE = "title"; /** @@ -100,6 +109,21 @@ public final class Slice implements Parcelable { */ public static final String HINT_NO_TINT = "no_tint"; /** + * Hint to indicate that this content should not be shown in the {@link SliceView#MODE_SMALL} + * and {@link SliceView#MODE_LARGE} modes of SliceView. This content may be used to populate + * the {@link SliceView#MODE_SHORTCUT} format of the slice. + * @hide + */ + public static final String HINT_HIDDEN = "hidden"; + /** + * Hint to indicate that this content has a toggle action associated with it. To indicate that + * the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the intent + * associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} which can be + * retrieved to see the new state of the toggle. + * @hide + */ + public static final String HINT_TOGGLE = "toggle"; + /** * Hint to indicate that this slice is incomplete and an update will be sent once * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the * OS and should not be cached by apps. @@ -112,6 +136,11 @@ public final class Slice implements Parcelable { * @hide */ public static final String HINT_ALT = "alt"; + /** + * Key to retrieve an extra added to an intent when a control is changed. + * @hide + */ + public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; private final SliceItem[] mItems; private final @SliceHint String[] mHints; @@ -398,4 +427,58 @@ public final class Slice implements Parcelable { resolver.releaseProvider(provider); } } + + /** + * Turns a slice intent into slice content. Expects an explicit intent. If there is no + * {@link ContentProvider} associated with the given intent this will throw + * {@link IllegalArgumentException}. + * + * @param context The context to use. + * @param intent The intent associated with a slice. + * @return The Slice provided by the app or null if none is given. + * @see Slice + * @see SliceProvider#onMapIntentToUri(Intent) + * @see Intent + */ + public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) { + Preconditions.checkNotNull(intent, "intent"); + Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, + "Slice intent must be explicit " + intent); + ContentResolver resolver = context.getContentResolver(); + + // Check if the intent has data for the slice uri on it and use that + final Uri intentData = intent.getData(); + if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { + return bindSlice(resolver, intentData); + } + // Otherwise ask the app + List<ResolveInfo> providers = + context.getPackageManager().queryIntentContentProviders(intent, 0); + if (providers == null) { + throw new IllegalArgumentException("Unable to resolve intent " + intent); + } + String authority = providers.get(0).providerInfo.authority; + Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).build(); + IContentProvider provider = resolver.acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); + final Bundle res = provider.call(resolver.getPackageName(), + SliceProvider.METHOD_MAP_INTENT, null, extras); + if (res == null) { + return null; + } + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + resolver.releaseProvider(provider); + } + } } diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java index 33825b4b..05f4ce6e 100644 --- a/android/app/slice/SliceProvider.java +++ b/android/app/slice/SliceProvider.java @@ -16,46 +16,69 @@ package android.app.slice; import android.Manifest.permission; +import android.annotation.NonNull; +import android.app.slice.widget.SliceView; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Intent; +import android.content.IntentFilter; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; +import android.os.Process; import android.os.StrictMode; import android.os.StrictMode.ThreadPolicy; +import android.os.UserHandle; import android.util.Log; import java.util.concurrent.CountDownLatch; /** - * A SliceProvider allows app to provide content to be displayed in system - * spaces. This content is templated and can contain actions, and the behavior - * of how it is surfaced is specific to the system surface. + * A SliceProvider allows an app to provide content to be displayed in system spaces. This content + * is templated and can contain actions, and the behavior of how it is surfaced is specific to the + * system surface. + * <p> + * Slices are not currently live content. They are bound once and shown to the user. If the content + * changes due to a callback from user interaction, then + * {@link ContentResolver#notifyChange(Uri, ContentObserver)} should be used to notify the system. + * </p> + * <p> + * The provider needs to be declared in the manifest to provide the authority for the app. The + * authority for most slices is expected to match the package of the application. + * </p> * - * <p>Slices are not currently live content. They are bound once and shown to the - * user. If the content changes due to a callback from user interaction, then - * {@link ContentResolver#notifyChange(Uri, ContentObserver)} - * should be used to notify the system.</p> - * - * <p>The provider needs to be declared in the manifest to provide the authority - * for the app. The authority for most slices is expected to match the package - * of the application.</p> * <pre class="prettyprint"> * {@literal * <provider * android:name="com.android.mypkg.MySliceProvider" * android:authorities="com.android.mypkg" />} * </pre> + * <p> + * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider + * must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via + * an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an + * appropriate Uri representing the slice. + * + * <pre class="prettyprint"> + * {@literal + * <provider + * android:name="com.android.mypkg.MySliceProvider" + * android:authorities="com.android.mypkg"> + * <intent-filter> + * <action android:name="android.intent.action.MY_SLICE_INTENT" /> + * </intent-filter> + * </provider>} + * </pre> * * @see Slice */ public abstract class SliceProvider extends ContentProvider { - /** * This is the Android platform's MIME type for a slice: URI * containing a slice implemented through {@link SliceProvider}. @@ -74,6 +97,14 @@ public abstract class SliceProvider extends ContentProvider { /** * @hide */ + public static final String METHOD_MAP_INTENT = "map_slice"; + /** + * @hide + */ + public static final String EXTRA_INTENT = "slice_intent"; + /** + * @hide + */ public static final String EXTRA_SLICE = "slice"; private static final boolean DEBUG = false; @@ -94,6 +125,20 @@ public abstract class SliceProvider extends ContentProvider { // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)). public abstract Slice onBindSlice(Uri sliceUri); + /** + * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider. + * In that case, this method can be called and is expected to return a non-null Uri representing + * a slice. Otherwise this will throw {@link UnsupportedOperationException}. + * + * @return Uri representing the slice associated with the provided intent. + * @see {@link Slice} + * @see {@link SliceView#setSlice(Intent)} + */ + public @NonNull Uri onMapIntentToUri(Intent intent) { + throw new UnsupportedOperationException( + "This provider has not implemented intent to uri mapping"); + } + @Override public final int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { @@ -143,14 +188,31 @@ public abstract class SliceProvider extends ContentProvider { @Override public Bundle call(String method, String arg, Bundle extras) { if (method.equals(METHOD_SLICE)) { - getContext().enforceCallingPermission(permission.BIND_SLICE, - "Slice binding requires the permission BIND_SLICE"); Uri uri = extras.getParcelable(EXTRA_BIND_URI); + if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { + getContext().enforceUriPermission(uri, permission.BIND_SLICE, + permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(), + Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + "Slice binding requires the permission BIND_SLICE"); + } Slice s = handleBindSlice(uri); Bundle b = new Bundle(); b.putParcelable(EXTRA_SLICE, s); return b; + } else if (method.equals(METHOD_MAP_INTENT)) { + getContext().enforceCallingPermission(permission.BIND_SLICE, + "Slice binding requires the permission BIND_SLICE"); + Intent intent = extras.getParcelable(EXTRA_INTENT); + Uri uri = onMapIntentToUri(intent); + Bundle b = new Bundle(); + if (uri != null) { + Slice s = handleBindSlice(uri); + b.putParcelable(EXTRA_SLICE, s); + } else { + b.putParcelable(EXTRA_SLICE, null); + } + return b; } return super.call(method, arg, extras); } diff --git a/android/app/slice/widget/GridView.java b/android/app/slice/widget/GridView.java index 67a3c671..793abc05 100644 --- a/android/app/slice/widget/GridView.java +++ b/android/app/slice/widget/GridView.java @@ -126,6 +126,9 @@ public class GridView extends LinearLayout implements SliceListView { * Returns true if this item is just an image. */ private boolean addItem(SliceItem item) { + if (item.hasHint(Slice.HINT_HIDDEN)) { + return false; + } if (item.getType() == SliceItem.TYPE_IMAGE) { ImageView v = new ImageView(getContext()); v.setImageIcon(item.getIcon()); @@ -145,6 +148,9 @@ public class GridView extends LinearLayout implements SliceListView { items.addAll(item.getSlice().getItems()); } items.forEach(i -> { + if (i.hasHint(Slice.HINT_HIDDEN)) { + return; + } Context context = getContext(); switch (i.getType()) { case SliceItem.TYPE_TEXT: diff --git a/android/app/slice/widget/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java index f45b2a8f..788f6fb6 100644 --- a/android/app/slice/widget/LargeTemplateView.java +++ b/android/app/slice/widget/LargeTemplateView.java @@ -85,9 +85,14 @@ public class LargeTemplateView extends SliceModeView { addList(slice, items); } else { slice.getItems().forEach(item -> { - if (item.hasHint(Slice.HINT_ACTIONS)) { + if (item.hasHint(Slice.HINT_HIDDEN)) { + // If it's hidden we don't show it + return; + } else if (item.hasHint(Slice.HINT_ACTIONS)) { + // Action groups don't show in lists return; } else if (item.getType() == SliceItem.TYPE_COLOR) { + // A color is not a list item return; } else if (item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST)) { @@ -108,8 +113,12 @@ public class LargeTemplateView extends SliceModeView { private void addList(Slice slice, List<SliceItem> items) { List<SliceItem> sliceItems = slice.getItems(); - sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM)); - items.addAll(sliceItems); + sliceItems.forEach(i -> { + if (!i.hasHint(Slice.HINT_HIDDEN) && i.getType() != SliceItem.TYPE_COLOR) { + i.addHint(Slice.HINT_LIST_ITEM); + items.add(i); + } + }); } /** diff --git a/android/app/slice/widget/ShortcutView.java b/android/app/slice/widget/ShortcutView.java index 0bca8ce2..0b7ad0d6 100644 --- a/android/app/slice/widget/ShortcutView.java +++ b/android/app/slice/widget/ShortcutView.java @@ -24,13 +24,20 @@ import android.app.slice.SliceQuery; import android.app.slice.widget.SliceView.SliceModeView; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.res.Resources; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.net.Uri; import com.android.internal.R; +import java.util.List; + /** * @hide */ @@ -38,27 +45,26 @@ public class ShortcutView extends SliceModeView { private static final String TAG = "ShortcutView"; - private PendingIntent mAction; private Uri mUri; + private PendingIntent mAction; + private SliceItem mLabel; + private SliceItem mIcon; + private int mLargeIconSize; private int mSmallIconSize; public ShortcutView(Context context) { super(context); - mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size); + final Resources res = getResources(); + mSmallIconSize = res.getDimensionPixelSize(R.dimen.slice_icon_size); + mLargeIconSize = res.getDimensionPixelSize(R.dimen.slice_shortcut_size); } @Override public void setSlice(Slice slice) { removeAllViews(); - SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION); - SliceItem iconItem = SliceQuery.getPrimaryIcon(slice); - SliceItem textItem = sliceItem != null - ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT) - : SliceQuery.find(slice, SliceItem.TYPE_TEXT); - SliceItem colorItem = sliceItem != null - ? SliceQuery.find(sliceItem, SliceItem.TYPE_COLOR) - : SliceQuery.find(slice, SliceItem.TYPE_COLOR); + determineShortcutItems(getContext(), slice); + SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR); if (colorItem == null) { colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR); } @@ -67,13 +73,11 @@ public class ShortcutView extends SliceModeView { ShapeDrawable circle = new ShapeDrawable(new OvalShape()); circle.setTint(color); setBackground(circle); - if (iconItem != null) { - final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE); + if (mIcon != null) { + final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE); final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize; - SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(), + SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(), isLarge, this /* parent */); - mAction = sliceItem != null ? sliceItem.getAction() - : null; mUri = slice.getUri(); setClickable(true); } else { @@ -103,4 +107,69 @@ public class ShortcutView extends SliceModeView { } return true; } + + /** + * Looks at the slice and determines which items are best to use to compose the shortcut. + */ + private void determineShortcutItems(Context context, Slice slice) { + List<String> h = slice.getHints(); + SliceItem sliceItem = new SliceItem(slice, SliceItem.TYPE_SLICE, + h.toArray(new String[h.size()])); + SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION, + Slice.HINT_TITLE, null); + + if (titleItem != null) { + // Preferred case: hinted action containing hinted image and text + mAction = titleItem.getAction(); + mIcon = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_IMAGE, Slice.HINT_TITLE, + null); + mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE, + null); + } else { + // No hinted action; just use the first one + SliceItem actionItem = SliceQuery.find(sliceItem, SliceItem.TYPE_ACTION, (String) null, + null); + mAction = (actionItem != null) ? actionItem.getAction() : null; + } + // First fallback: any hinted image and text + if (mIcon == null) { + mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE, + null); + } + if (mLabel == null) { + mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, + null); + } + // Second fallback: first image and text + if (mIcon == null) { + mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, (String) null, + null); + } + if (mLabel == null) { + mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, (String) null, + null); + } + // Final fallback: use app info + if (mIcon == null || mLabel == null || mAction == null) { + PackageManager pm = context.getPackageManager(); + ProviderInfo providerInfo = pm.resolveContentProvider( + slice.getUri().getAuthority(), 0); + ApplicationInfo appInfo = providerInfo.applicationInfo; + if (appInfo != null) { + if (mIcon == null) { + Drawable icon = appInfo.loadDefaultIcon(pm); + mIcon = new SliceItem(SliceViewUtil.createIconFromDrawable(icon), + SliceItem.TYPE_IMAGE, new String[] {Slice.HINT_LARGE}); + } + if (mLabel == null) { + mLabel = new SliceItem(pm.getApplicationLabel(appInfo), + SliceItem.TYPE_TEXT, null); + } + if (mAction == null) { + mAction = PendingIntent.getActivity(context, 0, + pm.getLaunchIntentForPackage(appInfo.packageName), 0); + } + } + } + } } diff --git a/android/app/slice/widget/SliceView.java b/android/app/slice/widget/SliceView.java index 5bafbc03..fa1b64ce 100644 --- a/android/app/slice/widget/SliceView.java +++ b/android/app/slice/widget/SliceView.java @@ -115,7 +115,9 @@ public class SliceView extends ViewGroup { */ public static final String MODE_LARGE = "SLICE_LARGE"; /** - * Mode indicating this slice should be presented as an icon. + * Mode indicating this slice should be presented as an icon. A shortcut requires an intent, + * icon, and label. This can be indicated by using {@link Slice#HINT_TITLE} on an action in a + * slice. */ public static final String MODE_SHORTCUT = "SLICE_ICON"; @@ -181,10 +183,25 @@ public class SliceView extends ViewGroup { } /** + * Populates this view with the {@link Slice} associated with the provided {@link Intent}. To + * use this method your app must have the permission + * {@link android.Manifest.permission#BIND_SLICE}). + * <p> + * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is + * updated with the slice identified by the provided intent changes. The lifecycle of this + * observer is handled by SliceView in {@link #onAttachedToWindow()} and + * {@link #onDetachedFromWindow()}. To unregister this observer outside of that you can call + * {@link #clearSlice}. + * + * @return true if a slice was found for the provided intent. * @hide */ - public void showSlice(Intent intent) { - // TODO + public boolean setSlice(@Nullable Intent intent) { + Slice s = Slice.bindSlice(mContext, intent); + if (s != null) { + return setSlice(s.getUri()); + } + return s != null; } /** @@ -197,8 +214,7 @@ public class SliceView extends ViewGroup { * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}. * To unregister this observer outside of that you can call {@link #clearSlice}. * - * @return true if the a slice was found for the provided uri. - * @see #clearSlice + * @return true if a slice was found for the provided uri. */ public boolean setSlice(@NonNull Uri sliceUri) { Preconditions.checkNotNull(sliceUri, @@ -210,11 +226,15 @@ public class SliceView extends ViewGroup { validate(sliceUri); Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri); if (s != null) { + if (mObserver != null) { + getContext().getContentResolver().unregisterContentObserver(mObserver); + } mObserver = new SliceObserver(new Handler(Looper.getMainLooper())); if (isAttachedToWindow()) { registerSlice(sliceUri); } - showSlice(s); + mCurrentSlice = s; + reinflate(); } return s != null; } diff --git a/android/app/slice/widget/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java index 03669983..1cf0055b 100644 --- a/android/app/slice/widget/SliceViewUtil.java +++ b/android/app/slice/widget/SliceViewUtil.java @@ -28,6 +28,7 @@ import android.graphics.Paint; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.view.Gravity; @@ -141,6 +142,21 @@ public class SliceViewUtil { /** * @hide */ + public static Icon createIconFromDrawable(Drawable d) { + if (d instanceof BitmapDrawable) { + return Icon.createWithBitmap(((BitmapDrawable) d).getBitmap()); + } + Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(b); + d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + d.draw(canvas); + return Icon.createWithBitmap(b); + } + + /** + * @hide + */ public static void createCircledIcon(Context context, int color, int iconSize, Icon icon, boolean isLarge, ViewGroup parent) { ImageView v = new ImageView(context); diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java index c827432a..3a3e16e0 100644 --- a/android/app/usage/UsageStatsManager.java +++ b/android/app/usage/UsageStatsManager.java @@ -261,7 +261,10 @@ public final class UsageStatsManager { /** * @hide + * Changes the app standby state to the provided bucket. */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) { try { mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId()); diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java index dbaace2f..29e7439f 100644 --- a/android/app/usage/UsageStatsManagerInternal.java +++ b/android/app/usage/UsageStatsManagerInternal.java @@ -118,7 +118,15 @@ public abstract class UsageStatsManagerInternal { AppIdleStateChangeListener listener); public static abstract class AppIdleStateChangeListener { - public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle); + + /** Callback to inform listeners that the idle state has changed to a new bucket. */ + public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle, + int bucket); + + /** + * Callback to inform listeners that the parole state has changed. This means apps are + * allowed to do work even if they're idle or in a low bucket. + */ public abstract void onParoleStateChanged(boolean isParoleOn); } |