summaryrefslogtreecommitdiff
path: root/android/app
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-11-17 16:38:15 -0500
committerJustin Klaassen <justinklaassen@google.com>2017-11-17 16:38:15 -0500
commit6a65f2da209bff03cb0eb6da309710ac6ee5026d (patch)
tree48e2090e716d4178378cb0599fc5d9cffbcf3f63 /android/app
parent46c77c203439b3b37c99d09e326df4b1fe08c10b (diff)
downloadandroid-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')
-rw-r--r--android/app/Activity.java91
-rw-r--r--android/app/ActivityManager.java40
-rw-r--r--android/app/ActivityManagerInternal.java21
-rw-r--r--android/app/ActivityOptions.java22
-rw-r--r--android/app/ActivityThread.java35
-rw-r--r--android/app/AppOpsManager.java12
-rw-r--r--android/app/DexLoadReporter.java24
-rw-r--r--android/app/DialogFragment.java3
-rw-r--r--android/app/Fragment.java12
-rw-r--r--android/app/FragmentBreadCrumbs.java3
-rw-r--r--android/app/FragmentContainer.java3
-rw-r--r--android/app/FragmentController.java3
-rw-r--r--android/app/FragmentHostCallback.java3
-rw-r--r--android/app/FragmentManager.java12
-rw-r--r--android/app/FragmentManagerNonConfig.java3
-rw-r--r--android/app/FragmentTransaction.java3
-rw-r--r--android/app/Instrumentation.java44
-rw-r--r--android/app/ListFragment.java3
-rw-r--r--android/app/LoaderManager.java6
-rw-r--r--android/app/Notification.java311
-rw-r--r--android/app/NotificationManager.java6
-rw-r--r--android/app/SystemServiceRegistry.java41
-rw-r--r--android/app/TimePickerDialog.java3
-rw-r--r--android/app/VrManager.java16
-rw-r--r--android/app/WindowConfiguration.java9
-rw-r--r--android/app/admin/DevicePolicyManager.java7
-rw-r--r--android/app/job/JobInfo.java229
-rw-r--r--android/app/slice/Slice.java85
-rw-r--r--android/app/slice/SliceProvider.java90
-rw-r--r--android/app/slice/widget/GridView.java6
-rw-r--r--android/app/slice/widget/LargeTemplateView.java15
-rw-r--r--android/app/slice/widget/ShortcutView.java99
-rw-r--r--android/app/slice/widget/SliceView.java32
-rw-r--r--android/app/slice/widget/SliceViewUtil.java16
-rw-r--r--android/app/usage/UsageStatsManager.java3
-rw-r--r--android/app/usage/UsageStatsManagerInternal.java10
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);
}