summaryrefslogtreecommitdiff
path: root/android/app
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-11-30 18:18:21 -0500
committerJustin Klaassen <justinklaassen@google.com>2017-11-30 18:18:21 -0500
commit4217cf85c20565a3446a662a7f07f26137b26b7f (patch)
treea0417b47a8cc802f6642f369fd2371165bec7b5c /android/app
parent6a65f2da209bff03cb0eb6da309710ac6ee5026d (diff)
downloadandroid-28-4217cf85c20565a3446a662a7f07f26137b26b7f.tar.gz
Import Android SDK Platform P [4477446]
/google/data/ro/projects/android/fetch_artifact \ --bid 4477446 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4477446.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: If0559643d7c328e36aafca98f0c114641d33642c
Diffstat (limited to 'android/app')
-rw-r--r--android/app/Activity.java2
-rw-r--r--android/app/ActivityManager.java8
-rw-r--r--android/app/ActivityManagerInternal.java16
-rw-r--r--android/app/ActivityThread.java439
-rw-r--r--android/app/ApplicationLoaders.java10
-rw-r--r--android/app/ApplicationPackageManager.java23
-rw-r--r--android/app/ClientTransactionHandler.java114
-rw-r--r--android/app/ContextImpl.java15
-rw-r--r--android/app/Notification.java25
-rw-r--r--android/app/NotificationManager.java66
-rw-r--r--android/app/ProfilerInfo.java29
-rw-r--r--android/app/ResultInfo.java27
-rw-r--r--android/app/SharedPreferencesImpl.java78
-rw-r--r--android/app/WindowConfiguration.java65
-rw-r--r--android/app/admin/ConnectEvent.java35
-rw-r--r--android/app/admin/DevicePolicyManager.java43
-rw-r--r--android/app/admin/DnsEvent.java51
-rw-r--r--android/app/admin/NetworkEvent.java31
-rw-r--r--android/app/assist/AssistStructure.java29
-rw-r--r--android/app/job/JobInfo.java35
-rw-r--r--android/app/job/JobService.java54
-rw-r--r--android/app/servertransaction/ActivityConfigurationChangeItem.java88
-rw-r--r--android/app/servertransaction/ActivityLifecycleItem.java44
-rw-r--r--android/app/servertransaction/ActivityResultItem.java95
-rw-r--r--android/app/servertransaction/BaseClientRequest.java45
-rw-r--r--android/app/servertransaction/ClientTransaction.java201
-rw-r--r--android/app/servertransaction/ClientTransactionItem.java54
-rw-r--r--android/app/servertransaction/ConfigurationChangeItem.java85
-rw-r--r--android/app/servertransaction/DestroyActivityItem.java99
-rw-r--r--android/app/servertransaction/LaunchActivityItem.java232
-rw-r--r--android/app/servertransaction/MoveToDisplayItem.java93
-rw-r--r--android/app/servertransaction/MultiWindowModeChangeItem.java92
-rw-r--r--android/app/servertransaction/NewIntentItem.java108
-rw-r--r--android/app/servertransaction/PauseActivityItem.java125
-rw-r--r--android/app/servertransaction/PipModeChangeItem.java89
-rw-r--r--android/app/servertransaction/ResumeActivityItem.java114
-rw-r--r--android/app/servertransaction/StopActivityItem.java112
-rw-r--r--android/app/servertransaction/WindowVisibilityItem.java85
-rw-r--r--android/app/slice/Slice.java205
-rw-r--r--android/app/slice/SliceItem.java187
-rw-r--r--android/app/slice/SliceProvider.java40
-rw-r--r--android/app/slice/SliceQuery.java55
-rw-r--r--android/app/slice/SliceSpec.java117
-rw-r--r--android/app/slice/widget/ActionRow.java201
-rw-r--r--android/app/slice/widget/GridView.java192
-rw-r--r--android/app/slice/widget/LargeSliceAdapter.java224
-rw-r--r--android/app/slice/widget/LargeTemplateView.java131
-rw-r--r--android/app/slice/widget/MessageView.java77
-rw-r--r--android/app/slice/widget/RemoteInputView.java445
-rw-r--r--android/app/slice/widget/ShortcutView.java175
-rw-r--r--android/app/slice/widget/SliceView.java422
-rw-r--r--android/app/slice/widget/SliceViewUtil.java198
-rw-r--r--android/app/slice/widget/SmallTemplateView.java211
-rw-r--r--android/app/usage/UsageEvents.java6
-rw-r--r--android/app/usage/UsageStatsManagerInternal.java14
55 files changed, 2909 insertions, 2947 deletions
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 99f3dee7..03a3631b 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -4379,7 +4379,7 @@ public class Activity extends ContextThemeWrapper
throw new IllegalArgumentException("requestCode should be >= 0");
}
if (mHasCurrentPermissionsRequest) {
- Log.w(TAG, "Can reqeust only one set of permissions at a time");
+ Log.w(TAG, "Can request only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 064e9782..02b7f8c5 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -519,11 +519,15 @@ public class ActivityManager {
* process that contains activities. */
public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16;
+ /** @hide Process is being cached for later use and has an activity that corresponds
+ * to an existing recent task. */
+ public static final int PROCESS_STATE_CACHED_RECENT = 17;
+
/** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 17;
+ public static final int PROCESS_STATE_CACHED_EMPTY = 18;
/** @hide Process does not exist. */
- public static final int PROCESS_STATE_NONEXISTENT = 18;
+ public static final int PROCESS_STATE_NONEXISTENT = 19;
// NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
// to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java
index a46b3c72..d7efa91f 100644
--- a/android/app/ActivityManagerInternal.java
+++ b/android/app/ActivityManagerInternal.java
@@ -283,4 +283,20 @@ public abstract class ActivityManagerInternal {
* @param token The IApplicationToken for the activity
*/
public abstract void setFocusedActivity(IBinder token);
+
+ /**
+ * Set a uid that is allowed to bypass stopped app switches, launching an app
+ * whenever it wants.
+ *
+ * @param type Type of the caller -- unique string the caller supplies to identify itself
+ * and disambiguate with other calles.
+ * @param uid The uid of the app to be allowed, or -1 to clear the uid for this type.
+ * @param userId The user it is allowed for.
+ */
+ public abstract void setAllowAppSwitches(@NonNull String type, int uid, int userId);
+
+ /**
+ * @return true if runtime was restarted, false if it's normal boot
+ */
+ public abstract boolean isRuntimeRestarted();
}
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index 21e454f1..ffd012d9 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -23,6 +23,8 @@ import android.annotation.Nullable;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.BackupAgent;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.ClientTransaction;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
@@ -174,7 +176,7 @@ final class RemoteServiceException extends AndroidRuntimeException {
*
* {@hide}
*/
-public final class ActivityThread {
+public final class ActivityThread extends ClientTransactionHandler {
/** @hide */
public static final String TAG = "ActivityThread";
private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
@@ -401,8 +403,8 @@ public final class ActivityThread {
throw new IllegalStateException(
"Received config update for non-existing activity");
}
- activity.mMainThread.handleActivityConfigurationChanged(
- new ActivityConfigChangeData(token, overrideConfig), newDisplayId);
+ activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
+ newDisplayId);
};
}
@@ -469,16 +471,6 @@ public final class ActivityThread {
}
}
- static final class NewIntentData {
- List<ReferrerIntent> intents;
- IBinder token;
- boolean andPause;
- public String toString() {
- return "NewIntentData{intents=" + intents + " token=" + token
- + " andPause=" + andPause +"}";
- }
- }
-
static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
boolean ordered, boolean sticky, IBinder token, int sendingUser) {
@@ -644,14 +636,6 @@ public final class ActivityThread {
String[] args;
}
- static final class ResultData {
- IBinder token;
- List<ResultInfo> results;
- public String toString() {
- return "ResultData{token=" + token + " results" + results + "}";
- }
- }
-
static final class ContextCleanupInfo {
ContextImpl context;
String what;
@@ -679,15 +663,6 @@ public final class ActivityThread {
int flags;
}
- static final class ActivityConfigChangeData {
- final IBinder activityToken;
- final Configuration overrideConfig;
- public ActivityConfigChangeData(IBinder token, Configuration config) {
- activityToken = token;
- overrideConfig = config;
- }
- }
-
private class ApplicationThread extends IApplicationThread.Stub {
private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
@@ -702,93 +677,10 @@ public final class ActivityThread {
}
}
- public final void schedulePauseActivity(IBinder token, boolean finished,
- boolean userLeaving, int configChanges, boolean dontReport) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "pauseActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- sendMessage(
- finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
- token,
- (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0),
- configChanges,
- seq);
- }
-
- public final void scheduleStopActivity(IBinder token, boolean showWindow,
- int configChanges) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "stopActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- sendMessage(
- showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
- token, 0, configChanges, seq);
- }
-
- public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
- sendMessage(
- showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
- token);
- }
-
public final void scheduleSleeping(IBinder token, boolean sleeping) {
sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
}
- public final void scheduleResumeActivity(IBinder token, int processState,
- boolean isForward, Bundle resumeArgs) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- updateProcessState(processState, false);
- sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);
- }
-
- public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
- ResultData res = new ResultData();
- res.token = token;
- res.results = results;
- sendMessage(H.SEND_RESULT, res);
- }
-
- // we use token to identify this activity without having to send the
- // activity itself back to the activity manager. (matters more with ipc)
- @Override
- public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
- CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
- int procState, Bundle state, PersistableBundle persistentState,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
-
- updateProcessState(procState, false);
-
- ActivityClientRecord r = new ActivityClientRecord();
-
- r.token = token;
- r.ident = ident;
- r.intent = intent;
- r.referrer = referrer;
- r.voiceInteractor = voiceInteractor;
- r.activityInfo = info;
- r.compatInfo = compatInfo;
- r.state = state;
- r.persistentState = persistentState;
-
- r.pendingResults = pendingResults;
- r.pendingIntents = pendingNewIntents;
-
- r.startsNotResumed = notResumed;
- r.isForward = isForward;
-
- r.profilerInfo = profilerInfo;
-
- r.overrideConfig = overrideConfig;
- updatePendingConfiguration(curConfig);
-
- sendMessage(H.LAUNCH_ACTIVITY, r);
- }
-
@Override
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
@@ -798,22 +690,6 @@ public final class ActivityThread {
configChanges, notResumed, config, overrideConfig, true, preserveWindow);
}
- public final void scheduleNewIntent(
- List<ReferrerIntent> intents, IBinder token, boolean andPause) {
- NewIntentData data = new NewIntentData();
- data.intents = intents;
- data.token = token;
- data.andPause = andPause;
-
- sendMessage(H.NEW_INTENT, data);
- }
-
- public final void scheduleDestroyActivity(IBinder token, boolean finishing,
- int configChanges) {
- sendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0,
- configChanges);
- }
-
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean sync, int sendingUser, int processState) {
@@ -949,11 +825,6 @@ public final class ActivityThread {
sendMessage(H.SUICIDE, null);
}
- public void scheduleConfigurationChanged(Configuration config) {
- updatePendingConfiguration(config);
- sendMessage(H.CONFIGURATION_CHANGED, config);
- }
-
public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
sendMessage(H.APPLICATION_INFO_CHANGED, ai);
}
@@ -1016,20 +887,6 @@ public final class ActivityThread {
}
@Override
- public void scheduleActivityConfigurationChanged(
- IBinder token, Configuration overrideConfig) {
- sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED,
- new ActivityConfigChangeData(token, overrideConfig));
- }
-
- @Override
- public void scheduleActivityMovedToDisplay(IBinder token, int displayId,
- Configuration overrideConfig) {
- sendMessage(H.ACTIVITY_MOVED_TO_DISPLAY,
- new ActivityConfigChangeData(token, overrideConfig), displayId);
- }
-
- @Override
public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
}
@@ -1427,26 +1284,6 @@ public final class ActivityThread {
}
@Override
- public void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
- Configuration overrideConfig) throws RemoteException {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = token;
- args.arg2 = overrideConfig;
- args.argi1 = isInMultiWindowMode ? 1 : 0;
- sendMessage(H.MULTI_WINDOW_MODE_CHANGED, args);
- }
-
- @Override
- public void schedulePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
- Configuration overrideConfig) throws RemoteException {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = token;
- args.arg2 = overrideConfig;
- args.argi1 = isInPipMode ? 1 : 0;
- sendMessage(H.PICTURE_IN_PICTURE_MODE_CHANGED, args);
- }
-
- @Override
public void scheduleLocalVoiceInteractionStarted(IBinder token,
IVoiceInteractor voiceInteractor) throws RemoteException {
SomeArgs args = SomeArgs.obtain();
@@ -1459,28 +1296,33 @@ public final class ActivityThread {
public void handleTrustStorageUpdate() {
NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate();
}
+
+ @Override
+ public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ ActivityThread.this.scheduleTransaction(transaction);
+ }
+ }
+
+ @Override
+ public void updatePendingConfiguration(Configuration config) {
+ mAppThread.updatePendingConfiguration(config);
+ }
+
+ @Override
+ public void updateProcessState(int processState, boolean fromIpc) {
+ mAppThread.updateProcessState(processState, fromIpc);
}
- private int getLifecycleSeq() {
+ @Override
+ public int getLifecycleSeq() {
synchronized (mResourcesManager) {
return mLifecycleSeq++;
}
}
- private class H extends Handler {
- public static final int LAUNCH_ACTIVITY = 100;
- public static final int PAUSE_ACTIVITY = 101;
- public static final int PAUSE_ACTIVITY_FINISHING= 102;
- public static final int STOP_ACTIVITY_SHOW = 103;
- public static final int STOP_ACTIVITY_HIDE = 104;
- public static final int SHOW_WINDOW = 105;
- public static final int HIDE_WINDOW = 106;
- public static final int RESUME_ACTIVITY = 107;
- public static final int SEND_RESULT = 108;
- public static final int DESTROY_ACTIVITY = 109;
+ class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
- public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
@@ -1493,7 +1335,6 @@ public final class ActivityThread {
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
- public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
@@ -1518,30 +1359,17 @@ public final class ActivityThread {
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
- public static final int MULTI_WINDOW_MODE_CHANGED = 152;
- public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
public static final int ATTACH_AGENT = 155;
public static final int APPLICATION_INFO_CHANGED = 156;
- public static final int ACTIVITY_MOVED_TO_DISPLAY = 157;
public static final int RUN_ISOLATED_ENTRY_POINT = 158;
+ public static final int EXECUTE_TRANSACTION = 159;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
- case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
- case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
- case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
- case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
- case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
- case SHOW_WINDOW: return "SHOW_WINDOW";
- case HIDE_WINDOW: return "HIDE_WINDOW";
- case RESUME_ACTIVITY: return "RESUME_ACTIVITY";
- case SEND_RESULT: return "SEND_RESULT";
- case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY";
case BIND_APPLICATION: return "BIND_APPLICATION";
case EXIT_APPLICATION: return "EXIT_APPLICATION";
- case NEW_INTENT: return "NEW_INTENT";
case RECEIVER: return "RECEIVER";
case CREATE_SERVICE: return "CREATE_SERVICE";
case SERVICE_ARGS: return "SERVICE_ARGS";
@@ -1553,8 +1381,6 @@ public final class ActivityThread {
case UNBIND_SERVICE: return "UNBIND_SERVICE";
case DUMP_SERVICE: return "DUMP_SERVICE";
case LOW_MEMORY: return "LOW_MEMORY";
- case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
- case ACTIVITY_MOVED_TO_DISPLAY: return "ACTIVITY_MOVED_TO_DISPLAY";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
@@ -1577,12 +1403,11 @@ public final class ActivityThread {
case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
- case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
- case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
case ATTACH_AGENT: return "ATTACH_AGENT";
case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
case RUN_ISOLATED_ENTRY_POINT: return "RUN_ISOLATED_ENTRY_POINT";
+ case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
}
}
return Integer.toString(code);
@@ -1590,76 +1415,12 @@ public final class ActivityThread {
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
- case LAUNCH_ACTIVITY: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
- final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
-
- r.packageInfo = getPackageInfoNoCheck(
- r.activityInfo.applicationInfo, r.compatInfo);
- handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
- case PAUSE_ACTIVITY: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- SomeArgs args = (SomeArgs) msg.obj;
- handlePauseActivity((IBinder) args.arg1, false,
- (args.argi1 & USER_LEAVING) != 0, args.argi2,
- (args.argi1 & DONT_REPORT) != 0, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case PAUSE_ACTIVITY_FINISHING: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- SomeArgs args = (SomeArgs) msg.obj;
- handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
- args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case STOP_ACTIVITY_SHOW: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- SomeArgs args = (SomeArgs) msg.obj;
- handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case STOP_ACTIVITY_HIDE: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- SomeArgs args = (SomeArgs) msg.obj;
- handleStopActivity((IBinder) args.arg1, false, args.argi2, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case SHOW_WINDOW:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
- handleWindowVisibility((IBinder)msg.obj, true);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case HIDE_WINDOW:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow");
- handleWindowVisibility((IBinder)msg.obj, false);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case RESUME_ACTIVITY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
- SomeArgs args = (SomeArgs) msg.obj;
- handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
- args.argi3, "RESUME_ACTIVITY");
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case SEND_RESULT:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
- handleSendResult((ResultData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case DESTROY_ACTIVITY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
- handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
- msg.arg2, false);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
@@ -1672,11 +1433,6 @@ public final class ActivityThread {
}
Looper.myLooper().quit();
break;
- case NEW_INTENT:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
- handleNewIntent((NewIntentData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
@@ -1708,15 +1464,7 @@ public final class ActivityThread {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CONFIGURATION_CHANGED:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
- mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
- mUpdatingSystemConfig = true;
- try {
- handleConfigurationChanged((Configuration) msg.obj, null);
- } finally {
- mUpdatingSystemConfig = false;
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ handleConfigurationChanged((Configuration) msg.obj);
break;
case CLEAN_UP_CONTEXT:
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
@@ -1733,18 +1481,6 @@ public final class ActivityThread {
handleLowMemory();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
- case ACTIVITY_CONFIGURATION_CHANGED:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
- handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
- INVALID_DISPLAY);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case ACTIVITY_MOVED_TO_DISPLAY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
- handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
- msg.arg1 /* displayId */);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case PROFILER_CONTROL:
handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
break;
@@ -1828,16 +1564,6 @@ public final class ActivityThread {
case STOP_BINDER_TRACKING_AND_DUMP:
handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
break;
- case MULTI_WINDOW_MODE_CHANGED:
- handleMultiWindowModeChanged((IBinder) ((SomeArgs) msg.obj).arg1,
- ((SomeArgs) msg.obj).argi1 == 1,
- (Configuration) ((SomeArgs) msg.obj).arg2);
- break;
- case PICTURE_IN_PICTURE_MODE_CHANGED:
- handlePictureInPictureModeChanged((IBinder) ((SomeArgs) msg.obj).arg1,
- ((SomeArgs) msg.obj).argi1 == 1,
- (Configuration) ((SomeArgs) msg.obj).arg2);
- break;
case LOCAL_VOICE_INTERACTION_STARTED:
handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
(IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
@@ -1857,6 +1583,9 @@ public final class ActivityThread {
handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
(String[]) ((SomeArgs) msg.obj).arg2);
break;
+ case EXECUTE_TRANSACTION:
+ ((ClientTransaction) msg.obj).execute(ActivityThread.this);
+ break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -2601,10 +2330,16 @@ public final class ActivityThread {
+ " req=" + requestCode + " res=" + resultCode + " data=" + data);
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(id, requestCode, resultCode, data));
- mAppThread.scheduleSendResult(token, list);
+ final ClientTransaction clientTransaction = new ClientTransaction(mAppThread, token);
+ clientTransaction.addCallback(new ActivityResultItem(list));
+ try {
+ mAppThread.scheduleTransaction(clientTransaction);
+ } catch (RemoteException e) {
+ // Local scheduling
+ }
}
- private void sendMessage(int what, Object obj) {
+ void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
@@ -2844,6 +2579,37 @@ public final class ActivityThread {
return appContext;
}
+ @Override
+ public void handleLaunchActivity(IBinder token, Intent intent, int ident, ActivityInfo info,
+ Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer,
+ IVoiceInteractor voiceInteractor, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
+ ActivityClientRecord r = new ActivityClientRecord();
+
+ r.token = token;
+ r.ident = ident;
+ r.intent = intent;
+ r.referrer = referrer;
+ r.voiceInteractor = voiceInteractor;
+ r.activityInfo = info;
+ r.compatInfo = compatInfo;
+ r.state = state;
+ r.persistentState = persistentState;
+
+ r.pendingResults = pendingResults;
+ r.pendingIntents = pendingNewIntents;
+
+ r.startsNotResumed = notResumed;
+ r.isForward = isForward;
+
+ r.profilerInfo = profilerInfo;
+
+ r.overrideConfig = overrideConfig;
+ r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
+ handleLaunchActivity(r, null /* customIntent */, "LAUNCH_ACTIVITY");
+ }
+
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -2974,8 +2740,9 @@ public final class ActivityThread {
}
}
- private void handleNewIntent(NewIntentData data) {
- performNewIntents(data.token, data.intents, data.andPause);
+ @Override
+ public void handleNewIntent(IBinder token, List<ReferrerIntent> intents, boolean andPause) {
+ performNewIntents(token, intents, andPause);
}
public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
@@ -3096,7 +2863,8 @@ public final class ActivityThread {
}
}
- private void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+ @Override
+ public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
Configuration overrideConfig) {
final ActivityClientRecord r = mActivities.get(token);
if (r != null) {
@@ -3108,7 +2876,8 @@ public final class ActivityThread {
}
}
- private void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+ @Override
+ public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
Configuration overrideConfig) {
final ActivityClientRecord r = mActivities.get(token);
if (r != null) {
@@ -3619,8 +3388,9 @@ public final class ActivityThread {
r.mPendingRemoveWindowManager = null;
}
- final void handleResumeActivity(IBinder token,
- boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
+ @Override
+ public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+ boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
@@ -3823,7 +3593,8 @@ public final class ActivityThread {
return thumbnail;
}
- private void handlePauseActivity(IBinder token, boolean finished,
+ @Override
+ public void handlePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport, int seq) {
ActivityClientRecord r = mActivities.get(token);
if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq);
@@ -4087,7 +3858,8 @@ public final class ActivityThread {
}
}
- private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
+ @Override
+ public void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "stopActivity")) {
return;
@@ -4142,7 +3914,8 @@ public final class ActivityThread {
}
}
- private void handleWindowVisibility(IBinder token, boolean show) {
+ @Override
+ public void handleWindowVisibility(IBinder token, boolean show) {
ActivityClientRecord r = mActivities.get(token);
if (r == null) {
@@ -4288,8 +4061,9 @@ public final class ActivityThread {
}
}
- private void handleSendResult(ResultData res) {
- ActivityClientRecord r = mActivities.get(res.token);
+ @Override
+ public void handleSendResult(IBinder token, List<ResultInfo> results) {
+ ActivityClientRecord r = mActivities.get(token);
if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
if (r != null) {
final boolean resumed = !r.paused;
@@ -4323,7 +4097,7 @@ public final class ActivityThread {
}
}
checkAndBlockForNetworkAccess();
- deliverResults(r, res.results);
+ deliverResults(r, results);
if (resumed) {
r.activity.performResume();
r.activity.mTemporaryPause = false;
@@ -4410,8 +4184,9 @@ public final class ActivityThread {
return component == null ? "[Unknown]" : component.toShortString();
}
- private void handleDestroyActivity(IBinder token, boolean finishing,
- int configChanges, boolean getNonConfigInstance) {
+ @Override
+ public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+ boolean getNonConfigInstance) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance);
if (r != null) {
@@ -4982,7 +4757,20 @@ public final class ActivityThread {
return config;
}
- final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
+ @Override
+ public void handleConfigurationChanged(Configuration config) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
+ mCurDefaultDisplayDpi = config.densityDpi;
+ mUpdatingSystemConfig = true;
+ try {
+ handleConfigurationChanged(config, null /* compat */);
+ } finally {
+ mUpdatingSystemConfig = false;
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
int configDiff = 0;
@@ -5113,12 +4901,15 @@ public final class ActivityThread {
/**
* Handle new activity configuration and/or move to a different display.
- * @param data Configuration update data.
+ * @param activityToken Target activity token.
+ * @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
*/
- void handleActivityConfigurationChanged(ActivityConfigChangeData data, int displayId) {
- ActivityClientRecord r = mActivities.get(data.activityToken);
+ @Override
+ public void handleActivityConfigurationChanged(IBinder activityToken,
+ Configuration overrideConfig, int displayId) {
+ ActivityClientRecord r = mActivities.get(activityToken);
// Check input params.
if (r == null || r.activity == null) {
if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
@@ -5128,14 +4919,14 @@ public final class ActivityThread {
&& displayId != r.activity.getDisplay().getDisplayId();
// Perform updates.
- r.overrideConfig = data.overrideConfig;
+ r.overrideConfig = overrideConfig;
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
if (movedToDifferentDisplay) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
+ r.activityInfo.name + ", displayId=" + displayId
- + ", config=" + data.overrideConfig);
+ + ", config=" + overrideConfig);
final Configuration reportedConfig = performConfigurationChangedForActivity(r,
mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
@@ -5144,7 +4935,7 @@ public final class ActivityThread {
}
} else {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
- + r.activityInfo.name + ", config=" + data.overrideConfig);
+ + r.activityInfo.name + ", config=" + overrideConfig);
performConfigurationChangedForActivity(r, mCompatConfiguration);
}
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
@@ -5380,7 +5171,7 @@ public final class ActivityThread {
}
}
- GraphicsEnvironment.chooseDriver(context);
+ GraphicsEnvironment.getInstance().setup(context);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/android/app/ApplicationLoaders.java b/android/app/ApplicationLoaders.java
index b7c1f4e0..72570442 100644
--- a/android/app/ApplicationLoaders.java
+++ b/android/app/ApplicationLoaders.java
@@ -17,9 +17,12 @@
package android.app;
import android.os.Build;
+import android.os.GraphicsEnvironment;
import android.os.Trace;
import android.util.ArrayMap;
+
import com.android.internal.os.ClassLoaderFactory;
+
import dalvik.system.PathClassLoader;
/** @hide */
@@ -72,8 +75,9 @@ public class ApplicationLoaders {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupVulkanLayerPath");
- setupVulkanLayerPath(classloader, librarySearchPath);
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setLayerPaths");
+ GraphicsEnvironment.getInstance().setLayerPaths(
+ classloader, librarySearchPath, libraryPermittedPath);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
mLoaders.put(cacheKey, classloader);
@@ -105,8 +109,6 @@ public class ApplicationLoaders {
cacheKey, null /* classLoaderName */);
}
- private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
-
/**
* Adds a new path the classpath of the given loader.
* @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}.
diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java
index 0eafdec6..005b7c38 100644
--- a/android/app/ApplicationPackageManager.java
+++ b/android/app/ApplicationPackageManager.java
@@ -35,7 +35,6 @@ import android.content.pm.FeatureInfo;
import android.content.pm.IOnPermissionsChangeListener;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
@@ -1681,21 +1680,8 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
- public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
- String installerPackageName) {
- installCommon(packageURI, new LegacyPackageInstallObserver(observer), flags,
- installerPackageName, mContext.getUserId());
- }
-
- @Override
- public void installPackage(Uri packageURI, PackageInstallObserver observer,
- int flags, String installerPackageName) {
- installCommon(packageURI, observer, flags, installerPackageName, mContext.getUserId());
- }
-
- private void installCommon(Uri packageURI,
- PackageInstallObserver observer, int flags, String installerPackageName,
- int userId) {
+ public void installPackage(Uri packageURI,
+ PackageInstallObserver observer, int flags, String installerPackageName) {
if (!"file".equals(packageURI.getScheme())) {
throw new UnsupportedOperationException("Only file:// URIs are supported");
}
@@ -1703,7 +1689,7 @@ public class ApplicationPackageManager extends PackageManager {
final String originPath = packageURI.getPath();
try {
mPM.installPackageAsUser(originPath, observer.getBinder(), flags, installerPackageName,
- userId);
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2476,7 +2462,8 @@ public class ApplicationPackageManager extends PackageManager {
if (itemInfo.showUserIcon != UserHandle.USER_NULL) {
Bitmap bitmap = getUserManager().getUserIcon(itemInfo.showUserIcon);
if (bitmap == null) {
- return UserIcons.getDefaultUserIcon(itemInfo.showUserIcon, /* light= */ false);
+ return UserIcons.getDefaultUserIcon(
+ mContext.getResources(), itemInfo.showUserIcon, /* light= */ false);
}
return new BitmapDrawable(bitmap);
}
diff --git a/android/app/ClientTransactionHandler.java b/android/app/ClientTransactionHandler.java
new file mode 100644
index 00000000..f7f4c716
--- /dev/null
+++ b/android/app/ClientTransactionHandler.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.app.servertransaction.ClientTransaction;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+
+/**
+ * Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
+ * can perform on client.
+ * @hide
+ */
+public abstract class ClientTransactionHandler {
+
+ // Schedule phase related logic and handlers.
+
+ /** Prepare and schedule transaction for execution. */
+ void scheduleTransaction(ClientTransaction transaction) {
+ transaction.prepare(this);
+ sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
+ }
+
+ abstract void sendMessage(int what, Object obj);
+
+
+ // Prepare phase related logic and handlers. Methods that inform about about pending changes or
+ // do other internal bookkeeping.
+
+ /** Get current lifecycle request number to maintain correct ordering. */
+ public abstract int getLifecycleSeq();
+
+ /** Set pending config in case it will be updated by other transaction item. */
+ public abstract void updatePendingConfiguration(Configuration config);
+
+ /** Set current process state. */
+ public abstract void updateProcessState(int processState, boolean fromIpc);
+
+
+ // Execute phase related logic and handlers. Methods here execute actual lifecycle transactions
+ // and deliver callbacks.
+
+ /** Destroy the activity. */
+ public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+ boolean getNonConfigInstance);
+
+ /** Pause the activity. */
+ public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+ int configChanges, boolean dontReport, int seq);
+
+ /** Resume the activity. */
+ public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+ boolean reallyResume, int seq, String reason);
+
+ /** Stop the activity. */
+ public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
+ int seq);
+
+ /** Deliver activity (override) configuration change. */
+ public abstract void handleActivityConfigurationChanged(IBinder activityToken,
+ Configuration overrideConfig, int displayId);
+
+ /** Deliver result from another activity. */
+ public abstract void handleSendResult(IBinder token, List<ResultInfo> results);
+
+ /** Deliver multi-window mode change notification. */
+ public abstract void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+ Configuration overrideConfig);
+
+ /** Deliver new intent. */
+ public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents,
+ boolean andPause);
+
+ /** Deliver picture-in-picture mode change notification. */
+ public abstract void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+ Configuration overrideConfig);
+
+ /** Update window visibility. */
+ public abstract void handleWindowVisibility(IBinder token, boolean show);
+
+ /** Perform activity launch. */
+ public abstract void handleLaunchActivity(IBinder token, Intent intent, int ident,
+ ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ String referrer, IVoiceInteractor voiceInteractor, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
+ ProfilerInfo profilerInfo);
+
+ /** Deliver app configuration change notification. */
+ public abstract void handleConfigurationChanged(Configuration config);
+}
diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java
index 5f343226..b0d020a7 100644
--- a/android/app/ContextImpl.java
+++ b/android/app/ContextImpl.java
@@ -59,11 +59,10 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -2457,7 +2456,8 @@ class ContextImpl extends Context {
* unable to create, they are filtered by replacing with {@code null}.
*/
private File[] ensureExternalDirsExistOrFilter(File[] dirs) {
- File[] result = new File[dirs.length];
+ final StorageManager sm = getSystemService(StorageManager.class);
+ final File[] result = new File[dirs.length];
for (int i = 0; i < dirs.length; i++) {
File dir = dirs[i];
if (!dir.exists()) {
@@ -2466,15 +2466,8 @@ class ContextImpl extends Context {
if (!dir.exists()) {
// Failing to mkdir() may be okay, since we might not have
// enough permissions; ask vold to create on our behalf.
- final IStorageManager storageManager = IStorageManager.Stub.asInterface(
- ServiceManager.getService("mount"));
try {
- final int res = storageManager.mkdirs(
- getPackageName(), dir.getAbsolutePath());
- if (res != 0) {
- Log.w(TAG, "Failed to ensure " + dir + ": " + res);
- dir = null;
- }
+ sm.mkdirs(dir);
} catch (Exception e) {
Log.w(TAG, "Failed to ensure " + dir + ": " + e);
dir = null;
diff --git a/android/app/Notification.java b/android/app/Notification.java
index d5d95fb8..42c1347e 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -68,6 +68,7 @@ import android.text.style.TextAppearanceSpan;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import android.view.Gravity;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -2447,6 +2448,30 @@ public class Notification implements Parcelable
notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);
}
+ /**
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(NotificationProto.CHANNEL_ID, getChannelId());
+ proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null);
+ proto.write(NotificationProto.FLAGS, this.flags);
+ proto.write(NotificationProto.COLOR, this.color);
+ proto.write(NotificationProto.CATEGORY, this.category);
+ proto.write(NotificationProto.GROUP_KEY, this.mGroupKey);
+ proto.write(NotificationProto.SORT_KEY, this.mSortKey);
+ if (this.actions != null) {
+ proto.write(NotificationProto.ACTION_LENGTH, this.actions.length);
+ }
+ if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) {
+ proto.write(NotificationProto.VISIBILITY, this.visibility);
+ }
+ if (publicVersion != null) {
+ publicVersion.writeToProto(proto, NotificationProto.PUBLIC_VERSION);
+ }
+ proto.end(token);
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index f931589b..659cf169 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -93,6 +93,58 @@ public class NotificationManager {
private static boolean localLOGV = false;
/**
+ * Intent that is broadcast when a {@link NotificationChannel} is blocked
+ * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
+ * (when {@link NotificationChannel#getImportance()} is anything other than
+ * {@link #IMPORTANCE_NONE}).
+ *
+ * This broadcast is only sent to the app that owns the channel that has changed.
+ *
+ * Input: nothing
+ * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED =
+ "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the
+ * object which has a new blocked state.
+ *
+ * The value will be the {@link NotificationChannel#getId()} of the channel for
+ * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and
+ * the {@link NotificationChannelGroup#getId()} of the group for
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}.
+ */
+ public static final String EXTRA_BLOCK_STATE_CHANGED_ID =
+ "android.app.extra.BLOCK_STATE_CHANGED_ID";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the new blocked
+ * state as a boolean.
+ *
+ * The value will be {@code true} if this channel or group is now blocked and {@code false} if
+ * this channel or group is now unblocked.
+ */
+ public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+
+
+ /**
+ * Intent that is broadcast when a {@link NotificationChannelGroup} is
+ * {@link NotificationChannelGroup#isBlocked() blocked} or unblocked.
+ *
+ * This broadcast is only sent to the app that owns the channel group that has changed.
+ *
+ * Input: nothing
+ * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED =
+ "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
+
+ /**
* Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
* This broadcast is only sent to registered receivers.
*
@@ -504,6 +556,20 @@ public class NotificationManager {
}
/**
+ * Returns the notification channel group settings for a given channel group id.
+ *
+ * The channel group must belong to your package, or null will be returned.
+ */
+ public NotificationChannelGroup getNotificationChannelGroup(String channelGroupId) {
+ INotificationManager service = getService();
+ try {
+ return service.getNotificationChannelGroup(mContext.getPackageName(), channelGroupId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all notification channel groups belonging to the calling app.
*/
public List<NotificationChannelGroup> getNotificationChannelGroups() {
diff --git a/android/app/ProfilerInfo.java b/android/app/ProfilerInfo.java
index fad4798e..d5234278 100644
--- a/android/app/ProfilerInfo.java
+++ b/android/app/ProfilerInfo.java
@@ -22,6 +22,7 @@ import android.os.Parcelable;
import android.util.Slog;
import java.io.IOException;
+import java.util.Objects;
/**
* System private API for passing profiler settings.
@@ -132,4 +133,32 @@ public class ProfilerInfo implements Parcelable {
streamingOutput = in.readInt() != 0;
agent = in.readString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ProfilerInfo other = (ProfilerInfo) o;
+ // TODO: Also check #profileFd for equality.
+ return Objects.equals(profileFile, other.profileFile)
+ && autoStopProfiler == other.autoStopProfiler
+ && samplingInterval == other.samplingInterval
+ && streamingOutput == other.streamingOutput
+ && Objects.equals(agent, other.agent);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(profileFile);
+ result = 31 * result + samplingInterval;
+ result = 31 * result + (autoStopProfiler ? 1 : 0);
+ result = 31 * result + (streamingOutput ? 1 : 0);
+ result = 31 * result + Objects.hashCode(agent);
+ return result;
+ }
}
diff --git a/android/app/ResultInfo.java b/android/app/ResultInfo.java
index 5e0867c3..d5af08a6 100644
--- a/android/app/ResultInfo.java
+++ b/android/app/ResultInfo.java
@@ -20,6 +20,8 @@ import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* {@hide}
*/
@@ -79,4 +81,29 @@ public class ResultInfo implements Parcelable {
mData = null;
}
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof ResultInfo)) {
+ return false;
+ }
+ final ResultInfo other = (ResultInfo) obj;
+ final boolean intentsEqual = mData == null ? (other.mData == null)
+ : mData.filterEquals(other.mData);
+ return intentsEqual && Objects.equals(mResultWho, other.mResultWho)
+ && mResultCode == other.mResultCode
+ && mRequestCode == other.mRequestCode;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mRequestCode;
+ result = 31 * result + mResultCode;
+ result = 31 * result + Objects.hashCode(mResultWho);
+ if (mData != null) {
+ result = 31 * result + mData.filterHashCode();
+ }
+ return result;
+ }
}
diff --git a/android/app/SharedPreferencesImpl.java b/android/app/SharedPreferencesImpl.java
index 6ea08252..8c47598f 100644
--- a/android/app/SharedPreferencesImpl.java
+++ b/android/app/SharedPreferencesImpl.java
@@ -34,8 +34,6 @@ import dalvik.system.BlockGuard;
import libcore.io.IoUtils;
-import com.google.android.collect.Maps;
-
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedInputStream;
@@ -139,7 +137,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
- Map map = null;
+ Map<String, Object> map = null;
StructStat stat = null;
try {
stat = Os.stat(mFile.getPath());
@@ -148,7 +146,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
- map = XmlUtils.readMapXml(str);
+ map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
@@ -214,12 +212,14 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.put(listener, CONTENT);
}
}
+ @Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.remove(listener);
@@ -241,6 +241,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public Map<String, ?> getAll() {
synchronized (mLock) {
awaitLoadedLocked();
@@ -249,6 +250,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
@@ -258,6 +260,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
@Nullable
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
synchronized (mLock) {
@@ -267,6 +270,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public int getInt(String key, int defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -274,6 +278,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
return v != null ? v : defValue;
}
}
+ @Override
public long getLong(String key, long defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -281,6 +286,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
return v != null ? v : defValue;
}
}
+ @Override
public float getFloat(String key, float defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -288,6 +294,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
return v != null ? v : defValue;
}
}
+ @Override
public boolean getBoolean(String key, boolean defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -296,6 +303,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public boolean contains(String key) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -303,6 +311,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
+ @Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
@@ -347,71 +356,81 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
public final class EditorImpl implements Editor {
- private final Object mLock = new Object();
+ private final Object mEditorLock = new Object();
- @GuardedBy("mLock")
- private final Map<String, Object> mModified = Maps.newHashMap();
+ @GuardedBy("mEditorLock")
+ private final Map<String, Object> mModified = new HashMap<>();
- @GuardedBy("mLock")
+ @GuardedBy("mEditorLock")
private boolean mClear = false;
+ @Override
public Editor putString(String key, @Nullable String value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putStringSet(String key, @Nullable Set<String> values) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
+ @Override
public Editor putInt(String key, int value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putLong(String key, long value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putFloat(String key, float value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putBoolean(String key, boolean value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor remove(String key) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, this);
return this;
}
}
+ @Override
public Editor clear() {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mClear = true;
return this;
}
}
+ @Override
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
+ @Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
@@ -429,6 +448,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
+ @Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
@@ -471,13 +491,13 @@ final class SharedPreferencesImpl implements SharedPreferences {
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
- synchronized (mLock) {
+ synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
- if (!mMap.isEmpty()) {
+ if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
- mMap.clear();
+ mapToWriteToDisk.clear();
}
mClear = false;
}
@@ -489,18 +509,18 @@ final class SharedPreferencesImpl implements SharedPreferences {
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {
- if (!mMap.containsKey(k)) {
+ if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
- mMap.remove(k);
+ mapToWriteToDisk.remove(k);
} else {
- if (mMap.containsKey(k)) {
- Object existingValue = mMap.get(k);
+ if (mapToWriteToDisk.containsKey(k)) {
+ Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
- mMap.put(k, v);
+ mapToWriteToDisk.put(k, v);
}
changesMade = true;
@@ -522,6 +542,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
mapToWriteToDisk);
}
+ @Override
public boolean commit() {
long startTime = 0;
@@ -564,11 +585,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
} else {
// Run this function on the main thread.
- ActivityThread.sMainThreadHandler.post(new Runnable() {
- public void run() {
- notifyListeners(mcr);
- }
- });
+ ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
}
}
}
@@ -594,6 +611,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
+ @Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
@@ -646,7 +664,7 @@ final class SharedPreferencesImpl implements SharedPreferences {
return str;
}
- // Note: must hold mWritingToDiskLock
+ @GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
long startTime = 0;
long existsTime = 0;
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 2c1fad1c..80399ae6 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -40,6 +40,13 @@ import android.view.DisplayInfo;
*/
@TestApi
public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
+ /**
+ * bounds that can differ from app bounds, which may include things such as insets.
+ *
+ * TODO: Investigate combining with {@link mAppBounds}. Can the latter be a product of the
+ * former?
+ */
+ private Rect mBounds = new Rect();
/**
* {@link android.graphics.Rect} defining app bounds. The dimensions override usages of
@@ -117,22 +124,26 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
})
public @interface ActivityType {}
+ /** Bit that indicates that the {@link #mBounds} changed.
+ * @hide */
+ public static final int WINDOW_CONFIG_BOUNDS = 1 << 0;
/** Bit that indicates that the {@link #mAppBounds} changed.
* @hide */
- public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 0;
+ public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 1;
/** Bit that indicates that the {@link #mWindowingMode} changed.
* @hide */
- public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 1;
+ public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 2;
/** Bit that indicates that the {@link #mActivityType} changed.
* @hide */
- public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 2;
+ public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 3;
/** @hide */
@IntDef(flag = true,
value = {
+ WINDOW_CONFIG_BOUNDS,
WINDOW_CONFIG_APP_BOUNDS,
WINDOW_CONFIG_WINDOWING_MODE,
- WINDOW_CONFIG_ACTIVITY_TYPE,
+ WINDOW_CONFIG_ACTIVITY_TYPE
})
public @interface WindowConfig {}
@@ -151,12 +162,14 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mBounds, flags);
dest.writeParcelable(mAppBounds, flags);
dest.writeInt(mWindowingMode);
dest.writeInt(mActivityType);
}
private void readFromParcel(Parcel source) {
+ mBounds = source.readParcelable(Rect.class.getClassLoader());
mAppBounds = source.readParcelable(Rect.class.getClassLoader());
mWindowingMode = source.readInt();
mActivityType = source.readInt();
@@ -181,6 +194,19 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
};
/**
+ * Sets the bounds to the provided {@link Rect}.
+ * @param rect the new bounds value.
+ */
+ public void setBounds(Rect rect) {
+ if (rect == null) {
+ mBounds.setEmpty();
+ return;
+ }
+
+ mBounds.set(rect);
+ }
+
+ /**
* Set {@link #mAppBounds} to the input Rect.
* @param rect The rect value to set {@link #mAppBounds} to.
* @see #getAppBounds()
@@ -212,6 +238,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
return mAppBounds;
}
+ /** @see #setBounds(Rect) */
+ public Rect getBounds() {
+ return mBounds;
+ }
+
public void setWindowingMode(@WindowingMode int windowingMode) {
mWindowingMode = windowingMode;
}
@@ -244,6 +275,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
public void setTo(WindowConfiguration other) {
+ setBounds(other.mBounds);
setAppBounds(other.mAppBounds);
setWindowingMode(other.mWindowingMode);
setActivityType(other.mActivityType);
@@ -258,6 +290,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
/** @hide */
public void setToDefaults() {
setAppBounds(null);
+ setBounds(null);
setWindowingMode(WINDOWING_MODE_UNDEFINED);
setActivityType(ACTIVITY_TYPE_UNDEFINED);
}
@@ -272,6 +305,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
*/
public @WindowConfig int updateFrom(@NonNull WindowConfiguration delta) {
int changed = 0;
+ // Only allow override if bounds is not empty
+ if (!delta.mBounds.isEmpty() && !delta.mBounds.equals(mBounds)) {
+ changed |= WINDOW_CONFIG_BOUNDS;
+ setBounds(delta.mBounds);
+ }
if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) {
changed |= WINDOW_CONFIG_APP_BOUNDS;
setAppBounds(delta.mAppBounds);
@@ -303,6 +341,10 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
public @WindowConfig long diff(WindowConfiguration other, boolean compareUndefined) {
long changes = 0;
+ if (!mBounds.equals(other.mBounds)) {
+ changes |= WINDOW_CONFIG_BOUNDS;
+ }
+
// Make sure that one of the values is not null and that they are not equal.
if ((compareUndefined || other.mAppBounds != null)
&& mAppBounds != other.mAppBounds
@@ -340,6 +382,16 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
n = mAppBounds.bottom - that.mAppBounds.bottom;
if (n != 0) return n;
}
+
+ n = mBounds.left - that.mBounds.left;
+ if (n != 0) return n;
+ n = mBounds.top - that.mBounds.top;
+ if (n != 0) return n;
+ n = mBounds.right - that.mBounds.right;
+ if (n != 0) return n;
+ n = mBounds.bottom - that.mBounds.bottom;
+ if (n != 0) return n;
+
n = mWindowingMode - that.mWindowingMode;
if (n != 0) return n;
n = mActivityType - that.mActivityType;
@@ -367,6 +419,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
if (mAppBounds != null) {
result = 31 * result + mAppBounds.hashCode();
}
+ result = 31 * result + mBounds.hashCode();
+
result = 31 * result + mWindowingMode;
result = 31 * result + mActivityType;
return result;
@@ -375,7 +429,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
/** @hide */
@Override
public String toString() {
- return "{mAppBounds=" + mAppBounds
+ return "{ mBounds=" + mBounds
+ + " mAppBounds=" + mAppBounds
+ " mWindowingMode=" + windowingModeToString(mWindowingMode)
+ " mActivityType=" + activityTypeToString(mActivityType) + "}";
}
diff --git a/android/app/admin/ConnectEvent.java b/android/app/admin/ConnectEvent.java
index ffd38e2b..f06a9257 100644
--- a/android/app/admin/ConnectEvent.java
+++ b/android/app/admin/ConnectEvent.java
@@ -32,29 +32,30 @@ import java.net.UnknownHostException;
public final class ConnectEvent extends NetworkEvent implements Parcelable {
/** The destination IP address. */
- private final String ipAddress;
+ private final String mIpAddress;
/** The destination port number. */
- private final int port;
+ private final int mPort;
/** @hide */
public ConnectEvent(String ipAddress, int port, String packageName, long timestamp) {
super(packageName, timestamp);
- this.ipAddress = ipAddress;
- this.port = port;
+ this.mIpAddress = ipAddress;
+ this.mPort = port;
}
private ConnectEvent(Parcel in) {
- this.ipAddress = in.readString();
- this.port = in.readInt();
- this.packageName = in.readString();
- this.timestamp = in.readLong();
+ this.mIpAddress = in.readString();
+ this.mPort = in.readInt();
+ this.mPackageName = in.readString();
+ this.mTimestamp = in.readLong();
+ this.mId = in.readLong();
}
public InetAddress getInetAddress() {
try {
// ipAddress is already an address, not a host name, no DNS resolution will happen.
- return InetAddress.getByName(ipAddress);
+ return InetAddress.getByName(mIpAddress);
} catch (UnknownHostException e) {
// Should never happen as we aren't passing a host name.
return InetAddress.getLoopbackAddress();
@@ -62,13 +63,13 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable {
}
public int getPort() {
- return port;
+ return mPort;
}
@Override
public String toString() {
- return String.format("ConnectEvent(%s, %d, %d, %s)", ipAddress, port, timestamp,
- packageName);
+ return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp,
+ mPackageName);
}
public static final Parcelable.Creator<ConnectEvent> CREATOR
@@ -96,10 +97,10 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
// write parcel token first
out.writeInt(PARCEL_TOKEN_CONNECT_EVENT);
- out.writeString(ipAddress);
- out.writeInt(port);
- out.writeString(packageName);
- out.writeLong(timestamp);
+ out.writeString(mIpAddress);
+ out.writeInt(mPort);
+ out.writeString(mPackageName);
+ out.writeLong(mTimestamp);
+ out.writeLong(mId);
}
}
-
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index f0226b7e..0bca9690 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -3858,6 +3858,47 @@ public class DevicePolicyManager {
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
+ return installKeyPair(admin, privKey, certs, alias, requestAccess, true);
+ }
+
+ /**
+ * Called by a device or profile owner, or delegated certificate installer, to install a
+ * certificate chain and corresponding private key for the leaf certificate. All apps within the
+ * profile will be able to access the certificate chain and use the private key, given direct
+ * user approval (if the user is allowed to select the private key).
+ *
+ * <p>The caller of this API may grant itself access to the certificate and private key
+ * immediately, without user approval. It is a best practice not to request this unless strictly
+ * necessary since it opens up additional security vulnerabilities.
+ *
+ * <p>Whether this key is offered to the user for approval at all or not depends on the
+ * {@code isUserSelectable} parameter.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if calling from a delegated certificate installer.
+ * @param privKey The private key to install.
+ * @param certs The certificate chain to install. The chain should start with the leaf
+ * certificate and include the chain of trust in order. This will be returned by
+ * {@link android.security.KeyChain#getCertificateChain}.
+ * @param alias The private key alias under which to install the certificate. If a certificate
+ * with that alias already exists, it will be overwritten.
+ * @param requestAccess {@code true} to request that the calling app be granted access to the
+ * credentials immediately. Otherwise, access to the credentials will be gated by user
+ * approval.
+ * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+ * Certificate Selection prompt, false to indicate that this key can only be granted
+ * access by implementing
+ * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * @return {@code true} if the keys were installed, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+ * owner.
+ * @see android.security.KeyChain#getCertificateChain
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
+ */
+ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
+ @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess,
+ boolean isUserSelectable) {
throwIfParentInstance("installKeyPair");
try {
final byte[] pemCert = Credentials.convertToPem(certs[0]);
@@ -3868,7 +3909,7 @@ public class DevicePolicyManager {
final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
.getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
return mService.installKeyPair(admin, mContext.getPackageName(), pkcs8Key, pemCert,
- pemChain, alias, requestAccess);
+ pemChain, alias, requestAccess, isUserSelectable);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
diff --git a/android/app/admin/DnsEvent.java b/android/app/admin/DnsEvent.java
index f84c5b00..4ddf13e0 100644
--- a/android/app/admin/DnsEvent.java
+++ b/android/app/admin/DnsEvent.java
@@ -34,46 +34,47 @@ import java.util.List;
public final class DnsEvent extends NetworkEvent implements Parcelable {
/** The hostname that was looked up. */
- private final String hostname;
+ private final String mHostname;
/** Contains (possibly a subset of) the IP addresses returned. */
- private final String[] ipAddresses;
+ private final String[] mIpAddresses;
/**
* The number of IP addresses returned from the DNS lookup event. May be different from the
* length of ipAddresses if there were too many addresses to log.
*/
- private final int ipAddressesCount;
+ private final int mIpAddressesCount;
/** @hide */
public DnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
String packageName, long timestamp) {
super(packageName, timestamp);
- this.hostname = hostname;
- this.ipAddresses = ipAddresses;
- this.ipAddressesCount = ipAddressesCount;
+ this.mHostname = hostname;
+ this.mIpAddresses = ipAddresses;
+ this.mIpAddressesCount = ipAddressesCount;
}
private DnsEvent(Parcel in) {
- this.hostname = in.readString();
- this.ipAddresses = in.createStringArray();
- this.ipAddressesCount = in.readInt();
- this.packageName = in.readString();
- this.timestamp = in.readLong();
+ this.mHostname = in.readString();
+ this.mIpAddresses = in.createStringArray();
+ this.mIpAddressesCount = in.readInt();
+ this.mPackageName = in.readString();
+ this.mTimestamp = in.readLong();
+ this.mId = in.readLong();
}
/** Returns the hostname that was looked up. */
public String getHostname() {
- return hostname;
+ return mHostname;
}
/** Returns (possibly a subset of) the IP addresses returned. */
public List<InetAddress> getInetAddresses() {
- if (ipAddresses == null || ipAddresses.length == 0) {
+ if (mIpAddresses == null || mIpAddresses.length == 0) {
return Collections.emptyList();
}
- final List<InetAddress> inetAddresses = new ArrayList<>(ipAddresses.length);
- for (final String ipAddress : ipAddresses) {
+ final List<InetAddress> inetAddresses = new ArrayList<>(mIpAddresses.length);
+ for (final String ipAddress : mIpAddresses) {
try {
// ipAddress is already an address, not a host name, no DNS resolution will happen.
inetAddresses.add(InetAddress.getByName(ipAddress));
@@ -90,14 +91,14 @@ public final class DnsEvent extends NetworkEvent implements Parcelable {
* addresses to log.
*/
public int getTotalResolvedAddressCount() {
- return ipAddressesCount;
+ return mIpAddressesCount;
}
@Override
public String toString() {
- return String.format("DnsEvent(%s, %s, %d, %d, %s)", hostname,
- (ipAddresses == null) ? "NONE" : String.join(" ", ipAddresses),
- ipAddressesCount, timestamp, packageName);
+ return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname,
+ (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses),
+ mIpAddressesCount, mTimestamp, mPackageName);
}
public static final Parcelable.Creator<DnsEvent> CREATOR
@@ -125,11 +126,11 @@ public final class DnsEvent extends NetworkEvent implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
// write parcel token first
out.writeInt(PARCEL_TOKEN_DNS_EVENT);
- out.writeString(hostname);
- out.writeStringArray(ipAddresses);
- out.writeInt(ipAddressesCount);
- out.writeString(packageName);
- out.writeLong(timestamp);
+ out.writeString(mHostname);
+ out.writeStringArray(mIpAddresses);
+ out.writeInt(mIpAddressesCount);
+ out.writeString(mPackageName);
+ out.writeLong(mTimestamp);
+ out.writeLong(mId);
}
}
-
diff --git a/android/app/admin/NetworkEvent.java b/android/app/admin/NetworkEvent.java
index 2646c3fd..947e4fed 100644
--- a/android/app/admin/NetworkEvent.java
+++ b/android/app/admin/NetworkEvent.java
@@ -18,8 +18,8 @@ package android.app.admin;
import android.content.pm.PackageManager;
import android.os.Parcel;
-import android.os.Parcelable;
import android.os.ParcelFormatException;
+import android.os.Parcelable;
/**
* An abstract class that represents a network event.
@@ -32,10 +32,13 @@ public abstract class NetworkEvent implements Parcelable {
static final int PARCEL_TOKEN_CONNECT_EVENT = 2;
/** The package name of the UID that performed the query. */
- String packageName;
+ String mPackageName;
/** The timestamp of the event being reported in milliseconds. */
- long timestamp;
+ long mTimestamp;
+
+ /** The id of the event. */
+ long mId;
/** @hide */
NetworkEvent() {
@@ -44,8 +47,8 @@ public abstract class NetworkEvent implements Parcelable {
/** @hide */
NetworkEvent(String packageName, long timestamp) {
- this.packageName = packageName;
- this.timestamp = timestamp;
+ this.mPackageName = packageName;
+ this.mTimestamp = timestamp;
}
/**
@@ -53,7 +56,7 @@ public abstract class NetworkEvent implements Parcelable {
* {@link PackageManager#getNameForUid}.
*/
public String getPackageName() {
- return packageName;
+ return mPackageName;
}
/**
@@ -61,7 +64,20 @@ public abstract class NetworkEvent implements Parcelable {
* the time the event was reported and midnight, January 1, 1970 UTC.
*/
public long getTimestamp() {
- return timestamp;
+ return mTimestamp;
+ }
+
+ /** @hide */
+ public void setId(long id) {
+ this.mId = id;
+ }
+
+ /**
+ * Returns the id of the event, where the id monotonically increases for each event. The id
+ * is reset when the device reboots, and when network logging is enabled.
+ */
+ public long getId() {
+ return this.mId;
}
@Override
@@ -95,4 +111,3 @@ public abstract class NetworkEvent implements Parcelable {
@Override
public abstract void writeToParcel(Parcel out, int flags);
}
-
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index e491a4f9..da5569d2 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -359,6 +359,8 @@ public class AssistStructure implements Parcelable {
if (DEBUG_PARCEL) Log.d(TAG, "Finished reading: at " + mCurParcel.dataPosition()
+ ", avail=" + mCurParcel.dataAvail() + ", windows=" + mNumReadWindows
+ ", views=" + mNumReadViews);
+ mCurParcel.recycle();
+ mCurParcel = null; // Parcel cannot be used after recycled.
}
Parcel readParcel(int validateToken, int level) {
@@ -396,20 +398,23 @@ public class AssistStructure implements Parcelable {
private void fetchData() {
Parcel data = Parcel.obtain();
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeStrongBinder(mTransferToken);
- if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
- if (mCurParcel != null) {
- mCurParcel.recycle();
- }
- mCurParcel = Parcel.obtain();
try {
- mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
- } catch (RemoteException e) {
- Log.w(TAG, "Failure reading AssistStructure data", e);
- throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+ data.writeInterfaceToken(DESCRIPTOR);
+ data.writeStrongBinder(mTransferToken);
+ if (DEBUG_PARCEL) Log.d(TAG, "Requesting data with token " + mTransferToken);
+ if (mCurParcel != null) {
+ mCurParcel.recycle();
+ }
+ mCurParcel = Parcel.obtain();
+ try {
+ mChannel.transact(TRANSACTION_XFER, data, mCurParcel, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure reading AssistStructure data", e);
+ throw new IllegalStateException("Failure reading AssistStructure data: " + e);
+ }
+ } finally {
+ data.recycle();
}
- data.recycle();
mNumReadWindows = mNumReadViews = 0;
}
}
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index 530d84b4..7c40b4ea 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -244,6 +244,13 @@ public class JobInfo implements Parcelable {
public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
/**
+ * Allows this job to run despite doze restrictions as long as the app is in the foreground
+ * or on the temporary whitelist
+ * @hide
+ */
+ public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1;
+
+ /**
* @hide
*/
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -1333,6 +1340,30 @@ public class JobInfo implements Parcelable {
}
/**
+ * Setting this to true indicates that this job is important while the scheduling app
+ * is in the foreground or on the temporary whitelist for background restrictions.
+ * This means that the system will relax doze restrictions on this job during this time.
+ *
+ * Apps should use this flag only for short jobs that are essential for the app to function
+ * properly in the foreground.
+ *
+ * Note that once the scheduling app is no longer whitelisted from background restrictions
+ * and in the background, or the job failed due to unsatisfied constraints,
+ * this job should be expected to behave like other jobs without this flag.
+ *
+ * @param importantWhileForeground whether to relax doze restrictions for this job when the
+ * app is in the foreground. False by default.
+ */
+ public Builder setImportantWhileForeground(boolean importantWhileForeground) {
+ if (importantWhileForeground) {
+ mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
+ } else {
+ mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND);
+ }
+ return this;
+ }
+
+ /**
* Set whether or not to persist this job across device reboots.
*
* @param isPersisted True to indicate that the job will be written to
@@ -1395,6 +1426,10 @@ public class JobInfo implements Parcelable {
"persisted job");
}
}
+ if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) {
+ throw new IllegalArgumentException("An important while foreground job cannot "
+ + "have a time delay");
+ }
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
diff --git a/android/app/job/JobService.java b/android/app/job/JobService.java
index 69afed20..61afadab 100644
--- a/android/app/job/JobService.java
+++ b/android/app/job/JobService.java
@@ -72,6 +72,33 @@ public abstract class JobService extends Service {
}
/**
+ * Call this to inform the JobScheduler that the job has finished its work. When the
+ * system receives this message, it releases the wakelock being held for the job.
+ * <p>
+ * You can request that the job be scheduled again by passing {@code true} as
+ * the <code>wantsReschedule</code> parameter. This will apply back-off policy
+ * for the job; this policy can be adjusted through the
+ * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method
+ * when the job is originally scheduled. The job's initial
+ * requirements are preserved when jobs are rescheduled, regardless of backed-off
+ * policy.
+ * <p class="note">
+ * A job running while the device is dozing will not be rescheduled with the normal back-off
+ * policy. Instead, the job will be re-added to the queue and executed again during
+ * a future idle maintenance window.
+ * </p>
+ *
+ * @param params The parameters identifying this job, as supplied to
+ * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @param wantsReschedule {@code true} if this job should be rescheduled according
+ * to the back-off criteria specified when it was first scheduled; {@code false}
+ * otherwise.
+ */
+ public final void jobFinished(JobParameters params, boolean wantsReschedule) {
+ mEngine.jobFinished(params, wantsReschedule);
+ }
+
+ /**
* Called to indicate that the job has begun executing. Override this method with the
* logic for your job. Like all other component lifecycle callbacks, this method executes
* on your application's main thread.
@@ -127,31 +154,4 @@ public abstract class JobService extends Service {
* to end the job entirely. Regardless of the value returned, your job must stop executing.
*/
public abstract boolean onStopJob(JobParameters params);
-
- /**
- * Call this to inform the JobScheduler that the job has finished its work. When the
- * system receives this message, it releases the wakelock being held for the job.
- * <p>
- * You can request that the job be scheduled again by passing {@code true} as
- * the <code>wantsReschedule</code> parameter. This will apply back-off policy
- * for the job; this policy can be adjusted through the
- * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method
- * when the job is originally scheduled. The job's initial
- * requirements are preserved when jobs are rescheduled, regardless of backed-off
- * policy.
- * <p class="note">
- * A job running while the device is dozing will not be rescheduled with the normal back-off
- * policy. Instead, the job will be re-added to the queue and executed again during
- * a future idle maintenance window.
- * </p>
- *
- * @param params The parameters identifying this job, as supplied to
- * the job in the {@link #onStartJob(JobParameters)} callback.
- * @param wantsReschedule {@code true} if this job should be rescheduled according
- * to the back-off criteria specified when it was first scheduled; {@code false}
- * otherwise.
- */
- public final void jobFinished(JobParameters params, boolean wantsReschedule) {
- mEngine.jobFinished(params, wantsReschedule);
- }
}
diff --git a/android/app/servertransaction/ActivityConfigurationChangeItem.java b/android/app/servertransaction/ActivityConfigurationChangeItem.java
new file mode 100644
index 00000000..07001e2b
--- /dev/null
+++ b/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Activity configuration changed callback.
+ * @hide
+ */
+public class ActivityConfigurationChangeItem extends ClientTransactionItem {
+
+ private final Configuration mConfiguration;
+
+ public ActivityConfigurationChangeItem(Configuration configuration) {
+ mConfiguration = configuration;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
+ client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private ActivityConfigurationChangeItem(Parcel in) {
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<ActivityConfigurationChangeItem> CREATOR =
+ new Creator<ActivityConfigurationChangeItem>() {
+ public ActivityConfigurationChangeItem createFromParcel(Parcel in) {
+ return new ActivityConfigurationChangeItem(in);
+ }
+
+ public ActivityConfigurationChangeItem[] newArray(int size) {
+ return new ActivityConfigurationChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o;
+ return mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return mConfiguration.hashCode();
+ }
+}
diff --git a/android/app/servertransaction/ActivityLifecycleItem.java b/android/app/servertransaction/ActivityLifecycleItem.java
new file mode 100644
index 00000000..a64108db
--- /dev/null
+++ b/android/app/servertransaction/ActivityLifecycleItem.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Request for lifecycle state that an activity should reach.
+ * @hide
+ */
+public abstract class ActivityLifecycleItem extends ClientTransactionItem {
+
+ static final boolean DEBUG_ORDER = false;
+
+ @IntDef({UNDEFINED, RESUMED, PAUSED, STOPPED, DESTROYED})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface LifecycleState{}
+ public static final int UNDEFINED = -1;
+ public static final int RESUMED = 0;
+ public static final int PAUSED = 1;
+ public static final int STOPPED = 2;
+ public static final int DESTROYED = 3;
+
+ /** A final lifecycle state that an activity should reach. */
+ @LifecycleState
+ public abstract int getTargetState();
+}
diff --git a/android/app/servertransaction/ActivityResultItem.java b/android/app/servertransaction/ActivityResultItem.java
new file mode 100644
index 00000000..76664d8e
--- /dev/null
+++ b/android/app/servertransaction/ActivityResultItem.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.PAUSED;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ResultInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Trace;
+
+import java.util.List;
+
+/**
+ * Activity result delivery callback.
+ * @hide
+ */
+public class ActivityResultItem extends ClientTransactionItem {
+
+ private final List<ResultInfo> mResultInfoList;
+
+ public ActivityResultItem(List<ResultInfo> resultInfos) {
+ mResultInfoList = resultInfos;
+ }
+
+ @Override
+ public int getPreExecutionState() {
+ return PAUSED;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
+ client.handleSendResult(token, mResultInfoList);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mResultInfoList, flags);
+ }
+
+ /** Read from Parcel. */
+ private ActivityResultItem(Parcel in) {
+ mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
+ }
+
+ public static final Parcelable.Creator<ActivityResultItem> CREATOR =
+ new Parcelable.Creator<ActivityResultItem>() {
+ public ActivityResultItem createFromParcel(Parcel in) {
+ return new ActivityResultItem(in);
+ }
+
+ public ActivityResultItem[] newArray(int size) {
+ return new ActivityResultItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ActivityResultItem other = (ActivityResultItem) o;
+ return mResultInfoList.equals(other.mResultInfoList);
+ }
+
+ @Override
+ public int hashCode() {
+ return mResultInfoList.hashCode();
+ }
+}
diff --git a/android/app/servertransaction/BaseClientRequest.java b/android/app/servertransaction/BaseClientRequest.java
new file mode 100644
index 00000000..4bd01afb
--- /dev/null
+++ b/android/app/servertransaction/BaseClientRequest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+
+/**
+ * Base interface for individual requests from server to client.
+ * Each of them can be prepared before scheduling and, eventually, executed.
+ * @hide
+ */
+public interface BaseClientRequest {
+
+ /**
+ * Prepare the client request before scheduling.
+ * An example of this might be informing about pending updates for some values.
+ *
+ * @param client Target client handler.
+ * @param token Target activity token.
+ */
+ default void prepare(ClientTransactionHandler client, IBinder token) {
+ }
+
+ /**
+ * Execute the request.
+ * @param client Target client handler.
+ * @param token Target activity token.
+ */
+ void execute(ClientTransactionHandler client, IBinder token);
+}
diff --git a/android/app/servertransaction/ClientTransaction.java b/android/app/servertransaction/ClientTransaction.java
new file mode 100644
index 00000000..d2289ba0
--- /dev/null
+++ b/android/app/servertransaction/ClientTransaction.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.app.IApplicationThread;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A container that holds a sequence of messages, which may be sent to a client.
+ * This includes a list of callbacks and a final lifecycle state.
+ *
+ * @see com.android.server.am.ClientLifecycleManager
+ * @see ClientTransactionItem
+ * @see ActivityLifecycleItem
+ * @hide
+ */
+public class ClientTransaction implements Parcelable {
+
+ /** A list of individual callbacks to a client. */
+ private List<ClientTransactionItem> mActivityCallbacks;
+
+ /**
+ * Final lifecycle state in which the client activity should be after the transaction is
+ * executed.
+ */
+ private ActivityLifecycleItem mLifecycleStateRequest;
+
+ /** Target client. */
+ private IApplicationThread mClient;
+
+ /** Target client activity. Might be null if the entire transaction is targeting an app. */
+ private IBinder mActivityToken;
+
+ public ClientTransaction(IApplicationThread client, IBinder activityToken) {
+ mClient = client;
+ mActivityToken = activityToken;
+ }
+
+ /**
+ * Add a message to the end of the sequence of callbacks.
+ * @param activityCallback A single message that can contain a lifecycle request/callback.
+ */
+ public void addCallback(ClientTransactionItem activityCallback) {
+ if (mActivityCallbacks == null) {
+ mActivityCallbacks = new ArrayList<>();
+ }
+ mActivityCallbacks.add(activityCallback);
+ }
+
+ /**
+ * Set the lifecycle state in which the client should be after executing the transaction.
+ * @param stateRequest A lifecycle request initialized with right parameters.
+ */
+ public void setLifecycleStateRequest(ActivityLifecycleItem stateRequest) {
+ mLifecycleStateRequest = stateRequest;
+ }
+
+ /**
+ * Do what needs to be done while the transaction is being scheduled on the client side.
+ * @param clientTransactionHandler Handler on the client side that will executed all operations
+ * requested by transaction items.
+ */
+ public void prepare(android.app.ClientTransactionHandler clientTransactionHandler) {
+ if (mActivityCallbacks != null) {
+ final int size = mActivityCallbacks.size();
+ for (int i = 0; i < size; ++i) {
+ mActivityCallbacks.get(i).prepare(clientTransactionHandler, mActivityToken);
+ }
+ }
+ if (mLifecycleStateRequest != null) {
+ mLifecycleStateRequest.prepare(clientTransactionHandler, mActivityToken);
+ }
+ }
+
+ /**
+ * Execute the transaction.
+ * @param clientTransactionHandler Handler on the client side that will execute all operations
+ * requested by transaction items.
+ */
+ public void execute(android.app.ClientTransactionHandler clientTransactionHandler) {
+ if (mActivityCallbacks != null) {
+ final int size = mActivityCallbacks.size();
+ for (int i = 0; i < size; ++i) {
+ mActivityCallbacks.get(i).execute(clientTransactionHandler, mActivityToken);
+ }
+ }
+ if (mLifecycleStateRequest != null) {
+ mLifecycleStateRequest.execute(clientTransactionHandler, mActivityToken);
+ }
+ }
+
+ /**
+ * Schedule the transaction after it was initialized. It will be send to client and all its
+ * individual parts will be applied in the following sequence:
+ * 1. The client calls {@link #prepare(ClientTransactionHandler)}, which triggers all work that
+ * needs to be done before actually scheduling the transaction for callbacks and lifecycle
+ * state request.
+ * 2. The transaction message is scheduled.
+ * 3. The client calls {@link #execute(ClientTransactionHandler)}, which executes all callbacks
+ * and necessary lifecycle transitions.
+ */
+ public void schedule() throws RemoteException {
+ mClient.scheduleTransaction(this);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mClient.asBinder());
+ final boolean writeActivityToken = mActivityToken != null;
+ dest.writeBoolean(writeActivityToken);
+ if (writeActivityToken) {
+ dest.writeStrongBinder(mActivityToken);
+ }
+ dest.writeParcelable(mLifecycleStateRequest, flags);
+ final boolean writeActivityCallbacks = mActivityCallbacks != null;
+ dest.writeBoolean(writeActivityCallbacks);
+ if (writeActivityCallbacks) {
+ dest.writeParcelableList(mActivityCallbacks, flags);
+ }
+ }
+
+ /** Read from Parcel. */
+ private ClientTransaction(Parcel in) {
+ mClient = (IApplicationThread) in.readStrongBinder();
+ final boolean readActivityToken = in.readBoolean();
+ if (readActivityToken) {
+ mActivityToken = in.readStrongBinder();
+ }
+ mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader());
+ final boolean readActivityCallbacks = in.readBoolean();
+ if (readActivityCallbacks) {
+ mActivityCallbacks = new ArrayList<>();
+ in.readParcelableList(mActivityCallbacks, getClass().getClassLoader());
+ }
+ }
+
+ public static final Creator<ClientTransaction> CREATOR =
+ new Creator<ClientTransaction>() {
+ public ClientTransaction createFromParcel(Parcel in) {
+ return new ClientTransaction(in);
+ }
+
+ public ClientTransaction[] newArray(int size) {
+ return new ClientTransaction[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ClientTransaction other = (ClientTransaction) o;
+ return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+ && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
+ && mClient == other.mClient
+ && mActivityToken == other.mActivityToken;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mActivityCallbacks);
+ result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/ClientTransactionItem.java b/android/app/servertransaction/ClientTransactionItem.java
new file mode 100644
index 00000000..6f2cc007
--- /dev/null
+++ b/android/app/servertransaction/ClientTransactionItem.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+
+import android.os.Parcelable;
+
+/**
+ * A callback message to a client that can be scheduled and executed.
+ * Examples of these might be activity configuration change, multi-window mode change, activity
+ * result delivery etc.
+ *
+ * @see ClientTransaction
+ * @see com.android.server.am.ClientLifecycleManager
+ * @hide
+ */
+public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable {
+
+ /** Get the state in which this callback can be executed. */
+ @LifecycleState
+ public int getPreExecutionState() {
+ return UNDEFINED;
+ }
+
+ /** Get the state that must follow this callback. */
+ @LifecycleState
+ public int getPostExecutionState() {
+ return UNDEFINED;
+ }
+
+
+ // Parcelable
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/android/app/servertransaction/ConfigurationChangeItem.java b/android/app/servertransaction/ConfigurationChangeItem.java
new file mode 100644
index 00000000..055923ec
--- /dev/null
+++ b/android/app/servertransaction/ConfigurationChangeItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * App configuration change message.
+ * @hide
+ */
+public class ConfigurationChangeItem extends ClientTransactionItem {
+
+ private final Configuration mConfiguration;
+
+ public ConfigurationChangeItem(Configuration configuration) {
+ mConfiguration = new Configuration(configuration);
+ }
+
+ @Override
+ public void prepare(android.app.ClientTransactionHandler client, IBinder token) {
+ client.updatePendingConfiguration(mConfiguration);
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handleConfigurationChanged(mConfiguration);
+ }
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private ConfigurationChangeItem(Parcel in) {
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<ConfigurationChangeItem> CREATOR =
+ new Creator<ConfigurationChangeItem>() {
+ public ConfigurationChangeItem createFromParcel(Parcel in) {
+ return new ConfigurationChangeItem(in);
+ }
+
+ public ConfigurationChangeItem[] newArray(int size) {
+ return new ConfigurationChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ConfigurationChangeItem other = (ConfigurationChangeItem) o;
+ return mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return mConfiguration.hashCode();
+ }
+}
diff --git a/android/app/servertransaction/DestroyActivityItem.java b/android/app/servertransaction/DestroyActivityItem.java
new file mode 100644
index 00000000..38fd5fb6
--- /dev/null
+++ b/android/app/servertransaction/DestroyActivityItem.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Request to destroy an activity.
+ * @hide
+ */
+public class DestroyActivityItem extends ActivityLifecycleItem {
+
+ private final boolean mFinished;
+ private final int mConfigChanges;
+
+ public DestroyActivityItem(boolean finished, int configChanges) {
+ mFinished = finished;
+ mConfigChanges = configChanges;
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
+ client.handleDestroyActivity(token, mFinished, mConfigChanges,
+ false /* getNonConfigInstance */);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return DESTROYED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mFinished);
+ dest.writeInt(mConfigChanges);
+ }
+
+ /** Read from Parcel. */
+ private DestroyActivityItem(Parcel in) {
+ mFinished = in.readBoolean();
+ mConfigChanges = in.readInt();
+ }
+
+ public static final Creator<DestroyActivityItem> CREATOR =
+ new Creator<DestroyActivityItem>() {
+ public DestroyActivityItem createFromParcel(Parcel in) {
+ return new DestroyActivityItem(in);
+ }
+
+ public DestroyActivityItem[] newArray(int size) {
+ return new DestroyActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final DestroyActivityItem other = (DestroyActivityItem) o;
+ return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mFinished ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/LaunchActivityItem.java b/android/app/servertransaction/LaunchActivityItem.java
new file mode 100644
index 00000000..417ebac8
--- /dev/null
+++ b/android/app/servertransaction/LaunchActivityItem.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.app.ProfilerInfo;
+import android.app.ResultInfo;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.Trace;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request to launch an activity.
+ * @hide
+ */
+public class LaunchActivityItem extends ActivityLifecycleItem {
+
+ private final Intent mIntent;
+ private final int mIdent;
+ private final ActivityInfo mInfo;
+ private final Configuration mCurConfig;
+ private final Configuration mOverrideConfig;
+ private final CompatibilityInfo mCompatInfo;
+ private final String mReferrer;
+ private final IVoiceInteractor mVoiceInteractor;
+ private final int mProcState;
+ private final Bundle mState;
+ private final PersistableBundle mPersistentState;
+ private final List<ResultInfo> mPendingResults;
+ private final List<ReferrerIntent> mPendingNewIntents;
+ // TODO(lifecycler): use lifecycle request instead of this param.
+ private final boolean mNotResumed;
+ private final boolean mIsForward;
+ private final ProfilerInfo mProfilerInfo;
+
+ public LaunchActivityItem(Intent intent, int ident, ActivityInfo info,
+ Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
+ ProfilerInfo profilerInfo) {
+ mIntent = intent;
+ mIdent = ident;
+ mInfo = info;
+ mCurConfig = curConfig;
+ mOverrideConfig = overrideConfig;
+ mCompatInfo = compatInfo;
+ mReferrer = referrer;
+ mVoiceInteractor = voiceInteractor;
+ mProcState = procState;
+ mState = state;
+ mPersistentState = persistentState;
+ mPendingResults = pendingResults;
+ mPendingNewIntents = pendingNewIntents;
+ mNotResumed = notResumed;
+ mIsForward = isForward;
+ mProfilerInfo = profilerInfo;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ client.updateProcessState(mProcState, false);
+ client.updatePendingConfiguration(mCurConfig);
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
+ client.handleLaunchActivity(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo,
+ mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults,
+ mPendingNewIntents, mNotResumed, mIsForward, mProfilerInfo);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return mNotResumed ? PAUSED : RESUMED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write from Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mIntent, flags);
+ dest.writeInt(mIdent);
+ dest.writeTypedObject(mInfo, flags);
+ dest.writeTypedObject(mCurConfig, flags);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ dest.writeTypedObject(mCompatInfo, flags);
+ dest.writeString(mReferrer);
+ dest.writeStrongBinder(mVoiceInteractor != null ? mVoiceInteractor.asBinder() : null);
+ dest.writeInt(mProcState);
+ dest.writeBundle(mState);
+ dest.writePersistableBundle(mPersistentState);
+ dest.writeTypedList(mPendingResults, flags);
+ dest.writeTypedList(mPendingNewIntents, flags);
+ dest.writeBoolean(mNotResumed);
+ dest.writeBoolean(mIsForward);
+ dest.writeTypedObject(mProfilerInfo, flags);
+ }
+
+ /** Read from Parcel. */
+ private LaunchActivityItem(Parcel in) {
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mIdent = in.readInt();
+ mInfo = in.readTypedObject(ActivityInfo.CREATOR);
+ mCurConfig = in.readTypedObject(Configuration.CREATOR);
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ mCompatInfo = in.readTypedObject(CompatibilityInfo.CREATOR);
+ mReferrer = in.readString();
+ mVoiceInteractor = (IVoiceInteractor) in.readStrongBinder();
+ mProcState = in.readInt();
+ mState = in.readBundle(getClass().getClassLoader());
+ mPersistentState = in.readPersistableBundle(getClass().getClassLoader());
+ mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
+ mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ mNotResumed = in.readBoolean();
+ mIsForward = in.readBoolean();
+ mProfilerInfo = in.readTypedObject(ProfilerInfo.CREATOR);
+ }
+
+ public static final Creator<LaunchActivityItem> CREATOR =
+ new Creator<LaunchActivityItem>() {
+ public LaunchActivityItem createFromParcel(Parcel in) {
+ return new LaunchActivityItem(in);
+ }
+
+ public LaunchActivityItem[] newArray(int size) {
+ return new LaunchActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final LaunchActivityItem other = (LaunchActivityItem) o;
+ return mIntent.filterEquals(other.mIntent) && mIdent == other.mIdent
+ && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
+ && Objects.equals(mOverrideConfig, other.mOverrideConfig)
+ && Objects.equals(mCompatInfo, other.mCompatInfo)
+ && Objects.equals(mReferrer, other.mReferrer)
+ && mProcState == other.mProcState && areBundlesEqual(mState, other.mState)
+ && areBundlesEqual(mPersistentState, other.mPersistentState)
+ && Objects.equals(mPendingResults, other.mPendingResults)
+ && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
+ && mNotResumed == other.mNotResumed && mIsForward == other.mIsForward
+ && Objects.equals(mProfilerInfo, other.mProfilerInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mIntent.filterHashCode();
+ result = 31 * result + mIdent;
+ result = 31 * result + Objects.hashCode(mCurConfig);
+ result = 31 * result + Objects.hashCode(mOverrideConfig);
+ result = 31 * result + Objects.hashCode(mCompatInfo);
+ result = 31 * result + Objects.hashCode(mReferrer);
+ result = 31 * result + Objects.hashCode(mProcState);
+ result = 31 * result + (mState != null ? mState.size() : 0);
+ result = 31 * result + (mPersistentState != null ? mPersistentState.size() : 0);
+ result = 31 * result + Objects.hashCode(mPendingResults);
+ result = 31 * result + Objects.hashCode(mPendingNewIntents);
+ result = 31 * result + (mNotResumed ? 1 : 0);
+ result = 31 * result + (mIsForward ? 1 : 0);
+ result = 31 * result + Objects.hashCode(mProfilerInfo);
+ return result;
+ }
+
+ private boolean activityInfoEqual(ActivityInfo other) {
+ return mInfo.flags == other.flags && mInfo.maxAspectRatio == other.maxAspectRatio
+ && Objects.equals(mInfo.launchToken, other.launchToken)
+ && Objects.equals(mInfo.getComponentName(), other.getComponentName());
+ }
+
+ private static boolean areBundlesEqual(BaseBundle extras, BaseBundle newExtras) {
+ if (extras == null || newExtras == null) {
+ return extras == newExtras;
+ }
+
+ if (extras.size() != newExtras.size()) {
+ return false;
+ }
+
+ for (String key : extras.keySet()) {
+ if (key != null) {
+ final Object value = extras.get(key);
+ final Object newValue = newExtras.get(key);
+ if (!Objects.equals(value, newValue)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/android/app/servertransaction/MoveToDisplayItem.java b/android/app/servertransaction/MoveToDisplayItem.java
new file mode 100644
index 00000000..ccd80d88
--- /dev/null
+++ b/android/app/servertransaction/MoveToDisplayItem.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Activity move to a different display message.
+ * @hide
+ */
+public class MoveToDisplayItem extends ClientTransactionItem {
+
+ private final int mTargetDisplayId;
+ private final Configuration mConfiguration;
+
+ public MoveToDisplayItem(int targetDisplayId, Configuration configuration) {
+ mTargetDisplayId = targetDisplayId;
+ mConfiguration = configuration;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
+ client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTargetDisplayId);
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private MoveToDisplayItem(Parcel in) {
+ mTargetDisplayId = in.readInt();
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<MoveToDisplayItem> CREATOR = new Creator<MoveToDisplayItem>() {
+ public MoveToDisplayItem createFromParcel(Parcel in) {
+ return new MoveToDisplayItem(in);
+ }
+
+ public MoveToDisplayItem[] newArray(int size) {
+ return new MoveToDisplayItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MoveToDisplayItem other = (MoveToDisplayItem) o;
+ return mTargetDisplayId == other.mTargetDisplayId
+ && mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mTargetDisplayId;
+ result = 31 * result + mConfiguration.hashCode();
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/MultiWindowModeChangeItem.java b/android/app/servertransaction/MultiWindowModeChangeItem.java
new file mode 100644
index 00000000..a0c617fa
--- /dev/null
+++ b/android/app/servertransaction/MultiWindowModeChangeItem.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Multi-window mode change message.
+ * @hide
+ */
+// TODO(lifecycler): Remove the use of this and just use the configuration change message to
+// communicate multi-window mode change with WindowConfiguration.
+public class MultiWindowModeChangeItem extends ClientTransactionItem {
+
+ private final boolean mIsInMultiWindowMode;
+ private final Configuration mOverrideConfig;
+
+ public MultiWindowModeChangeItem(boolean isInMultiWindowMode,
+ Configuration overrideConfig) {
+ mIsInMultiWindowMode = isInMultiWindowMode;
+ mOverrideConfig = overrideConfig;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handleMultiWindowModeChanged(token, mIsInMultiWindowMode, mOverrideConfig);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsInMultiWindowMode);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ }
+
+ /** Read from Parcel. */
+ private MultiWindowModeChangeItem(Parcel in) {
+ mIsInMultiWindowMode = in.readBoolean();
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<MultiWindowModeChangeItem> CREATOR =
+ new Creator<MultiWindowModeChangeItem>() {
+ public MultiWindowModeChangeItem createFromParcel(Parcel in) {
+ return new MultiWindowModeChangeItem(in);
+ }
+
+ public MultiWindowModeChangeItem[] newArray(int size) {
+ return new MultiWindowModeChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MultiWindowModeChangeItem other = (MultiWindowModeChangeItem) o;
+ return mIsInMultiWindowMode == other.mIsInMultiWindowMode
+ && mOverrideConfig.equals(other.mOverrideConfig);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mIsInMultiWindowMode ? 1 : 0);
+ result = 31 * result + mOverrideConfig.hashCode();
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/NewIntentItem.java b/android/app/servertransaction/NewIntentItem.java
new file mode 100644
index 00000000..61a8965a
--- /dev/null
+++ b/android/app/servertransaction/NewIntentItem.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.PAUSED;
+import static android.app.servertransaction.ActivityLifecycleItem.RESUMED;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Trace;
+
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+
+/**
+ * New intent message.
+ * @hide
+ */
+public class NewIntentItem extends ClientTransactionItem {
+
+ private final List<ReferrerIntent> mIntents;
+ private final boolean mPause;
+
+ public NewIntentItem(List<ReferrerIntent> intents, boolean pause) {
+ mIntents = intents;
+ mPause = pause;
+ }
+
+ @Override
+ public int getPreExecutionState() {
+ return PAUSED;
+ }
+
+ @Override
+ public int getPostExecutionState() {
+ return RESUMED;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
+ client.handleNewIntent(token, mIntents, mPause);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mPause);
+ dest.writeTypedList(mIntents, flags);
+ }
+
+ /** Read from Parcel. */
+ private NewIntentItem(Parcel in) {
+ mPause = in.readBoolean();
+ mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ }
+
+ public static final Parcelable.Creator<NewIntentItem> CREATOR =
+ new Parcelable.Creator<NewIntentItem>() {
+ public NewIntentItem createFromParcel(Parcel in) {
+ return new NewIntentItem(in);
+ }
+
+ public NewIntentItem[] newArray(int size) {
+ return new NewIntentItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final NewIntentItem other = (NewIntentItem) o;
+ return mPause == other.mPause && mIntents.equals(other.mIntents);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mPause ? 1 : 0);
+ result = 31 * result + mIntents.hashCode();
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/PauseActivityItem.java b/android/app/servertransaction/PauseActivityItem.java
new file mode 100644
index 00000000..e561a4b5
--- /dev/null
+++ b/android/app/servertransaction/PauseActivityItem.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to paused state.
+ * @hide
+ */
+public class PauseActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "PauseActivityItem";
+
+ private final boolean mFinished;
+ private final boolean mUserLeaving;
+ private final int mConfigChanges;
+ private final boolean mDontReport;
+
+ private int mLifecycleSeq;
+
+ public PauseActivityItem(boolean finished, boolean userLeaving, int configChanges,
+ boolean dontReport) {
+ mFinished = finished;
+ mUserLeaving = userLeaving;
+ mConfigChanges = configChanges;
+ mDontReport = dontReport;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Pause transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
+ client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport,
+ mLifecycleSeq);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return PAUSED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mFinished);
+ dest.writeBoolean(mUserLeaving);
+ dest.writeInt(mConfigChanges);
+ dest.writeBoolean(mDontReport);
+ }
+
+ /** Read from Parcel. */
+ private PauseActivityItem(Parcel in) {
+ mFinished = in.readBoolean();
+ mUserLeaving = in.readBoolean();
+ mConfigChanges = in.readInt();
+ mDontReport = in.readBoolean();
+ }
+
+ public static final Creator<PauseActivityItem> CREATOR =
+ new Creator<PauseActivityItem>() {
+ public PauseActivityItem createFromParcel(Parcel in) {
+ return new PauseActivityItem(in);
+ }
+
+ public PauseActivityItem[] newArray(int size) {
+ return new PauseActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final PauseActivityItem other = (PauseActivityItem) o;
+ return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
+ && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mFinished ? 1 : 0);
+ result = 31 * result + (mUserLeaving ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ result = 31 * result + (mDontReport ? 1 : 0);
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/PipModeChangeItem.java b/android/app/servertransaction/PipModeChangeItem.java
new file mode 100644
index 00000000..923839ee
--- /dev/null
+++ b/android/app/servertransaction/PipModeChangeItem.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Picture in picture mode change message.
+ * @hide
+ */
+// TODO(lifecycler): Remove the use of this and just use the configuration change message to
+// communicate multi-window mode change with WindowConfiguration.
+public class PipModeChangeItem extends ClientTransactionItem {
+
+ private final boolean mIsInPipMode;
+ private final Configuration mOverrideConfig;
+
+ public PipModeChangeItem(boolean isInPipMode, Configuration overrideConfig) {
+ mIsInPipMode = isInPipMode;
+ mOverrideConfig = overrideConfig;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handlePictureInPictureModeChanged(token, mIsInPipMode, mOverrideConfig);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsInPipMode);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ }
+
+ /** Read from Parcel. */
+ private PipModeChangeItem(Parcel in) {
+ mIsInPipMode = in.readBoolean();
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<PipModeChangeItem> CREATOR =
+ new Creator<PipModeChangeItem>() {
+ public PipModeChangeItem createFromParcel(Parcel in) {
+ return new PipModeChangeItem(in);
+ }
+
+ public PipModeChangeItem[] newArray(int size) {
+ return new PipModeChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final PipModeChangeItem other = (PipModeChangeItem) o;
+ return mIsInPipMode == other.mIsInPipMode && mOverrideConfig.equals(other.mOverrideConfig);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mIsInPipMode ? 1 : 0);
+ result = 31 * result + mOverrideConfig.hashCode();
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/ResumeActivityItem.java b/android/app/servertransaction/ResumeActivityItem.java
new file mode 100644
index 00000000..ea31a461
--- /dev/null
+++ b/android/app/servertransaction/ResumeActivityItem.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to resumed state.
+ * @hide
+ */
+public class ResumeActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "ResumeActivityItem";
+
+ private final int mProcState;
+ private final boolean mIsForward;
+
+ private int mLifecycleSeq;
+
+ public ResumeActivityItem(int procState, boolean isForward) {
+ mProcState = procState;
+ mIsForward = isForward;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Resume transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ client.updateProcessState(mProcState, false);
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
+ client.handleResumeActivity(token, true /* clearHide */, mIsForward,
+ true /* reallyResume */, mLifecycleSeq, "RESUME_ACTIVITY");
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return RESUMED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mProcState);
+ dest.writeBoolean(mIsForward);
+ }
+
+ /** Read from Parcel. */
+ private ResumeActivityItem(Parcel in) {
+ mProcState = in.readInt();
+ mIsForward = in.readBoolean();
+ }
+
+ public static final Creator<ResumeActivityItem> CREATOR =
+ new Creator<ResumeActivityItem>() {
+ public ResumeActivityItem createFromParcel(Parcel in) {
+ return new ResumeActivityItem(in);
+ }
+
+ public ResumeActivityItem[] newArray(int size) {
+ return new ResumeActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ResumeActivityItem other = (ResumeActivityItem) o;
+ return mProcState == other.mProcState && mIsForward == other.mIsForward;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mProcState;
+ result = 31 * result + (mIsForward ? 1 : 0);
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/StopActivityItem.java b/android/app/servertransaction/StopActivityItem.java
new file mode 100644
index 00000000..d62c5077
--- /dev/null
+++ b/android/app/servertransaction/StopActivityItem.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to stopped state.
+ * @hide
+ */
+public class StopActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "StopActivityItem";
+
+ private final boolean mShowWindow;
+ private final int mConfigChanges;
+
+ private int mLifecycleSeq;
+
+ public StopActivityItem(boolean showWindow, int configChanges) {
+ mShowWindow = showWindow;
+ mConfigChanges = configChanges;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Stop transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
+ client.handleStopActivity(token, mShowWindow, mConfigChanges, mLifecycleSeq);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return STOPPED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mShowWindow);
+ dest.writeInt(mConfigChanges);
+ }
+
+ /** Read from Parcel. */
+ private StopActivityItem(Parcel in) {
+ mShowWindow = in.readBoolean();
+ mConfigChanges = in.readInt();
+ }
+
+ public static final Creator<StopActivityItem> CREATOR =
+ new Creator<StopActivityItem>() {
+ public StopActivityItem createFromParcel(Parcel in) {
+ return new StopActivityItem(in);
+ }
+
+ public StopActivityItem[] newArray(int size) {
+ return new StopActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final StopActivityItem other = (StopActivityItem) o;
+ return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mShowWindow ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ return result;
+ }
+}
diff --git a/android/app/servertransaction/WindowVisibilityItem.java b/android/app/servertransaction/WindowVisibilityItem.java
new file mode 100644
index 00000000..8e88b38d
--- /dev/null
+++ b/android/app/servertransaction/WindowVisibilityItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Window visibility change message.
+ * @hide
+ */
+public class WindowVisibilityItem extends ClientTransactionItem {
+
+ private final boolean mShowWindow;
+
+ public WindowVisibilityItem(boolean showWindow) {
+ mShowWindow = showWindow;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
+ client.handleWindowVisibility(token, mShowWindow);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mShowWindow);
+ }
+
+ /** Read from Parcel. */
+ private WindowVisibilityItem(Parcel in) {
+ mShowWindow = in.readBoolean();
+ }
+
+ public static final Creator<WindowVisibilityItem> CREATOR =
+ new Creator<WindowVisibilityItem>() {
+ public WindowVisibilityItem createFromParcel(Parcel in) {
+ return new WindowVisibilityItem(in);
+ }
+
+ public WindowVisibilityItem[] newArray(int size) {
+ return new WindowVisibilityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final WindowVisibilityItem other = (WindowVisibilityItem) o;
+ return mShowWindow == other.mShowWindow;
+ }
+
+ @Override
+ public int hashCode() {
+ return 17 + 31 * (mShowWindow ? 1 : 0);
+ }
+}
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index 616a5be3..ddc5760a 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -33,7 +33,6 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.widget.RemoteViews;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
@@ -41,6 +40,7 @@ import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* A slice is a piece of app content and actions that can be surfaced outside of the app.
@@ -54,16 +54,25 @@ public final class Slice implements Parcelable {
* @hide
*/
@StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
- HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL})
+ HINT_NO_TINT, HINT_PARTIAL})
public @interface SliceHint{ }
/**
+ * The meta-data key that allows an activity to easily be linked directly to a slice.
+ * <p>
+ * An activity can be statically linked to a slice uri by including a meta-data item
+ * for this key that contains a valid slice uri for the same application declaring
+ * the activity.
+ * @hide
+ */
+ public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+
+ /**
* 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
+ * @see SliceItem#FORMAT_ACTION
*/
public static final String HINT_TITLE = "title";
/**
@@ -91,27 +100,13 @@ public final class Slice implements Parcelable {
*/
public static final String HINT_SELECTED = "selected";
/**
- * Hint to indicate that this is a message as part of a communication
- * sequence in this slice.
- */
- public static final String HINT_MESSAGE = "message";
- /**
- * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}.
- */
- public static final String HINT_SOURCE = "source";
- /**
- * Hint that list items within this slice or subslice would appear better
- * if organized horizontally.
- */
- public static final String HINT_HORIZONTAL = "horizontal";
- /**
* Hint to indicate that this content should not be tinted.
*/
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.
+ * Hint to indicate that this content should not be shown in larger renderings
+ * of Slices. This content may be used to populate the shortcut/icon
+ * format of the slice.
* @hide
*/
public static final String HINT_HIDDEN = "hidden";
@@ -124,32 +119,42 @@ public final class Slice implements Parcelable {
*/
public static final String HINT_TOGGLE = "toggle";
/**
+ * Hint that list items within this slice or subslice would appear better
+ * if organized horizontally.
+ */
+ public static final String HINT_HORIZONTAL = "horizontal";
+ /**
* 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.
*/
public static final String HINT_PARTIAL = "partial";
- // These two are coming over from prototyping, but we probably don't want in
- // public API, at least not right now.
- /**
- * @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";
+ /**
+ * Subtype to indicate that this is a message as part of a communication
+ * sequence in this slice.
+ */
+ public static final String SUBTYPE_MESSAGE = "message";
+ /**
+ * Subtype to tag the source (i.e. sender) of a {@link #SUBTYPE_MESSAGE}.
+ */
+ public static final String SUBTYPE_SOURCE = "source";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
+ private SliceSpec mSpec;
private Uri mUri;
- Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+ Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri, SliceSpec spec) {
mHints = hints;
mItems = items.toArray(new SliceItem[items.size()]);
mUri = uri;
+ mSpec = spec;
}
protected Slice(Parcel in) {
@@ -160,6 +165,14 @@ public final class Slice implements Parcelable {
mItems[i] = SliceItem.CREATOR.createFromParcel(in);
}
mUri = Uri.CREATOR.createFromParcel(in);
+ mSpec = in.readTypedObject(SliceSpec.CREATOR);
+ }
+
+ /**
+ * @return The spec for this slice
+ */
+ public @Nullable SliceSpec getSpec() {
+ return mSpec;
}
/**
@@ -191,6 +204,7 @@ public final class Slice implements Parcelable {
mItems[i].writeToParcel(dest, flags);
}
mUri.writeToParcel(dest, 0);
+ dest.writeTypedObject(mSpec, flags);
}
@Override
@@ -213,6 +227,7 @@ public final class Slice implements Parcelable {
private final Uri mUri;
private ArrayList<SliceItem> mItems = new ArrayList<>();
private @SliceHint ArrayList<String> mHints = new ArrayList<>();
+ private SliceSpec mSpec;
/**
* Create a builder which will construct a {@link Slice} for the Given Uri.
@@ -248,11 +263,28 @@ public final class Slice implements Parcelable {
}
/**
+ * Add the spec for this slice.
+ */
+ public Builder setSpec(SliceSpec spec) {
+ mSpec = spec;
+ return this;
+ }
+
+ /**
* Add a sub-slice to the slice being constructed
*/
public Builder addSubSlice(@NonNull Slice slice) {
- mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray(
- new String[slice.getHints().size()])));
+ return addSubSlice(slice, null);
+ }
+
+ /**
+ * Add a sub-slice to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
+ */
+ public Builder addSubSlice(@NonNull Slice slice, @Nullable String subType) {
+ mItems.add(new SliceItem(slice, SliceItem.FORMAT_SLICE, subType,
+ slice.getHints().toArray(new String[slice.getHints().size()])));
return this;
}
@@ -260,99 +292,132 @@ public final class Slice implements Parcelable {
* Add an action to the slice being constructed
*/
public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
- mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0]));
- return this;
+ return addAction(action, s, null);
}
/**
- * Add text to the slice being constructed
+ * Add an action to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addText(CharSequence text, @SliceHint String... hints) {
- mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
+ public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s,
+ @Nullable String subType) {
+ List<String> hints = s.getHints();
+ s.mSpec = null;
+ mItems.add(new SliceItem(action, s, SliceItem.FORMAT_ACTION, subType, hints.toArray(
+ new String[hints.size()])));
return this;
}
/**
* Add text to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addText(CharSequence text, @SliceHint List<String> hints) {
- return addText(text, hints.toArray(new String[hints.size()]));
+ public Builder addText(CharSequence text, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(text, SliceItem.FORMAT_TEXT, subType, hints));
+ return this;
}
/**
- * Add an image to the slice being constructed
+ * Add text to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addIcon(Icon icon, @SliceHint String... hints) {
- mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
- return this;
+ public Builder addText(CharSequence text, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addText(text, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add an image to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addIcon(Icon icon, @SliceHint List<String> hints) {
- return addIcon(icon, hints.toArray(new String[hints.size()]));
+ public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint String... hints) {
+ mItems.add(new SliceItem(icon, SliceItem.FORMAT_IMAGE, subType, hints));
+ return this;
}
/**
- * @hide This isn't final
+ * Add an image to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
- return this;
+ public Builder addIcon(Icon icon, @Nullable String subType, @SliceHint List<String> hints) {
+ return addIcon(icon, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add remote input to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
@SliceHint List<String> hints) {
- return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
+ return addRemoteInput(remoteInput, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add remote input to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(remoteInput, SliceItem.FORMAT_REMOTE_INPUT,
+ subType, hints));
return this;
}
/**
* Add a color to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addColor(int color, @SliceHint String... hints) {
- mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
+ public Builder addColor(int color, @Nullable String subType, @SliceHint String... hints) {
+ mItems.add(new SliceItem(color, SliceItem.FORMAT_COLOR, subType, hints));
return this;
}
/**
* Add a color to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Builder addColor(int color, @SliceHint List<String> hints) {
- return addColor(color, hints.toArray(new String[hints.size()]));
+ public Builder addColor(int color, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addColor(color, subType, hints.toArray(new String[hints.size()]));
}
/**
* Add a timestamp to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
- mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
+ public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ @SliceHint String... hints) {
+ mItems.add(new SliceItem(time, SliceItem.FORMAT_TIMESTAMP, subType,
+ hints));
return this;
}
/**
* Add a timestamp to the slice being constructed
+ * @param subType Optional template-specific type information
+ * @see {@link SliceItem#getSubType()}
*/
- public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) {
- return addTimestamp(time, hints.toArray(new String[hints.size()]));
+ public Slice.Builder addTimestamp(long time, @Nullable String subType,
+ @SliceHint List<String> hints) {
+ return addTimestamp(time, subType, hints.toArray(new String[hints.size()]));
}
/**
* Construct the slice.
*/
public Slice build() {
- return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
+ return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri, mSpec);
}
}
@@ -380,15 +445,15 @@ public final class Slice implements Parcelable {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mItems.length; i++) {
sb.append(indent);
- if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
+ if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_SLICE)) {
sb.append("slice:\n");
sb.append(mItems[i].getSlice().toString(indent + " "));
- } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
+ } else if (Objects.equals(mItems[i].getFormat(), SliceItem.FORMAT_TEXT)) {
sb.append("text: ");
sb.append(mItems[i].getText());
sb.append("\n");
} else {
- sb.append(SliceItem.typeToString(mItems[i].getType()));
+ sb.append(mItems[i].getFormat());
sb.append("\n");
}
}
@@ -400,10 +465,12 @@ public final class Slice implements Parcelable {
*
* @param resolver ContentResolver to be used.
* @param uri The URI to a slice provider
+ * @param supportedSpecs List of supported specs.
* @return The Slice provided by the app or null if none is given.
* @see Slice
*/
- public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) {
+ public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri,
+ List<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider provider = resolver.acquireProvider(uri);
if (provider == null) {
@@ -412,6 +479,8 @@ public final class Slice implements Parcelable {
try {
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+ extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+ new ArrayList<>(supportedSpecs));
final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
null, extras);
Bundle.setDefusable(res, true);
@@ -435,12 +504,14 @@ public final class Slice implements Parcelable {
*
* @param context The context to use.
* @param intent The intent associated with a slice.
+ * @param supportedSpecs List of supported specs.
* @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) {
+ public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent,
+ List<SliceSpec> supportedSpecs) {
Preconditions.checkNotNull(intent, "intent");
Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
"Slice intent must be explicit " + intent);
@@ -449,7 +520,7 @@ public final class Slice implements Parcelable {
// 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);
+ return bindSlice(resolver, intentData, supportedSpecs);
}
// Otherwise ask the app
List<ResolveInfo> providers =
@@ -467,6 +538,8 @@ public final class Slice implements Parcelable {
try {
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+ extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+ new ArrayList<>(supportedSpecs));
final Bundle res = provider.call(resolver.getPackageName(),
SliceProvider.METHOD_MAP_INTENT, null, extras);
if (res == null) {
diff --git a/android/app/slice/SliceItem.java b/android/app/slice/SliceItem.java
index 6e69b051..cdeee357 100644
--- a/android/app/slice/SliceItem.java
+++ b/android/app/slice/SliceItem.java
@@ -16,8 +16,8 @@
package android.app.slice;
-import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.graphics.drawable.Icon;
@@ -38,13 +38,13 @@ import java.util.List;
*
* A SliceItem a piece of content and some hints about what that content
* means or how it should be displayed. The types of content can be:
- * <li>{@link #TYPE_SLICE}</li>
- * <li>{@link #TYPE_TEXT}</li>
- * <li>{@link #TYPE_IMAGE}</li>
- * <li>{@link #TYPE_ACTION}</li>
- * <li>{@link #TYPE_COLOR}</li>
- * <li>{@link #TYPE_TIMESTAMP}</li>
- * <li>{@link #TYPE_REMOTE_INPUT}</li>
+ * <li>{@link #FORMAT_SLICE}</li>
+ * <li>{@link #FORMAT_TEXT}</li>
+ * <li>{@link #FORMAT_IMAGE}</li>
+ * <li>{@link #FORMAT_ACTION}</li>
+ * <li>{@link #FORMAT_COLOR}</li>
+ * <li>{@link #FORMAT_TIMESTAMP}</li>
+ * <li>{@link #FORMAT_REMOTE_INPUT}</li>
*
* The hints that a {@link SliceItem} are a set of strings which annotate
* the content. The hints that are guaranteed to be understood by the system
@@ -55,68 +55,68 @@ public final class SliceItem implements Parcelable {
/**
* @hide
*/
- @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR,
- TYPE_TIMESTAMP, TYPE_REMOTE_INPUT})
+ @StringDef({FORMAT_SLICE, FORMAT_TEXT, FORMAT_IMAGE, FORMAT_ACTION, FORMAT_COLOR,
+ FORMAT_TIMESTAMP, FORMAT_REMOTE_INPUT})
public @interface SliceType {}
/**
* A {@link SliceItem} that contains a {@link Slice}
*/
- public static final int TYPE_SLICE = 1;
+ public static final String FORMAT_SLICE = "slice";
/**
* A {@link SliceItem} that contains a {@link CharSequence}
*/
- public static final int TYPE_TEXT = 2;
+ public static final String FORMAT_TEXT = "text";
/**
* A {@link SliceItem} that contains an {@link Icon}
*/
- public static final int TYPE_IMAGE = 3;
+ public static final String FORMAT_IMAGE = "image";
/**
* A {@link SliceItem} that contains a {@link PendingIntent}
*
* Note: Actions contain 2 pieces of data, In addition to the pending intent, the
* item contains a {@link Slice} that the action applies to.
*/
- public static final int TYPE_ACTION = 4;
- /**
- * @hide This isn't final
- */
- public static final int TYPE_REMOTE_VIEW = 5;
+ public static final String FORMAT_ACTION = "action";
/**
* A {@link SliceItem} that contains a Color int.
*/
- public static final int TYPE_COLOR = 6;
+ public static final String FORMAT_COLOR = "color";
/**
* A {@link SliceItem} that contains a timestamp.
*/
- public static final int TYPE_TIMESTAMP = 8;
+ public static final String FORMAT_TIMESTAMP = "timestamp";
/**
* A {@link SliceItem} that contains a {@link RemoteInput}.
*/
- public static final int TYPE_REMOTE_INPUT = 9;
+ public static final String FORMAT_REMOTE_INPUT = "input";
/**
* @hide
*/
protected @Slice.SliceHint
String[] mHints;
- private final int mType;
+ private final String mFormat;
+ private final String mSubType;
private final Object mObj;
/**
* @hide
*/
- public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
+ public SliceItem(Object obj, @SliceType String format, String subType,
+ @Slice.SliceHint String[] hints) {
mHints = hints;
- mType = type;
+ mFormat = format;
+ mSubType = subType;
mObj = obj;
}
/**
* @hide
*/
- public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
- this(new Pair<>(intent, slice), type, hints);
+ public SliceItem(PendingIntent intent, Slice slice, String format, String subType,
+ @Slice.SliceHint String[] hints) {
+ this(new Pair<>(intent, slice), format, subType, hints);
}
/**
@@ -141,26 +141,51 @@ public final class SliceItem implements Parcelable {
ArrayUtils.removeElement(String.class, mHints, hint);
}
- public @SliceType int getType() {
- return mType;
+ /**
+ * Get the format of this SliceItem.
+ * <p>
+ * The format will be one of the following types supported by the platform:
+ * <li>{@link #FORMAT_SLICE}</li>
+ * <li>{@link #FORMAT_TEXT}</li>
+ * <li>{@link #FORMAT_IMAGE}</li>
+ * <li>{@link #FORMAT_ACTION}</li>
+ * <li>{@link #FORMAT_COLOR}</li>
+ * <li>{@link #FORMAT_TIMESTAMP}</li>
+ * <li>{@link #FORMAT_REMOTE_INPUT}</li>
+ * @see #getSubType() ()
+ */
+ public String getFormat() {
+ return mFormat;
+ }
+
+ /**
+ * Get the sub-type of this SliceItem.
+ * <p>
+ * Subtypes provide additional information about the type of this information beyond basic
+ * interpretations inferred by {@link #getFormat()}. For example a slice may contain
+ * many {@link #FORMAT_TEXT} items, but only some of them may be {@link Slice#SUBTYPE_MESSAGE}.
+ * @see #getFormat()
+ */
+ public String getSubType() {
+ return mSubType;
}
/**
- * @return The text held by this {@link #TYPE_TEXT} SliceItem
+ * @return The text held by this {@link #FORMAT_TEXT} SliceItem
*/
public CharSequence getText() {
return (CharSequence) mObj;
}
/**
- * @return The icon held by this {@link #TYPE_IMAGE} SliceItem
+ * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem
*/
public Icon getIcon() {
return (Icon) mObj;
}
/**
- * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem
+ * @return The pending intent held by this {@link #FORMAT_ACTION} SliceItem
*/
public PendingIntent getAction() {
return ((Pair<PendingIntent, Slice>) mObj).first;
@@ -174,31 +199,31 @@ public final class SliceItem implements Parcelable {
}
/**
- * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem
+ * @return The remote input held by this {@link #FORMAT_REMOTE_INPUT} SliceItem
*/
public RemoteInput getRemoteInput() {
return (RemoteInput) mObj;
}
/**
- * @return The color held by this {@link #TYPE_COLOR} SliceItem
+ * @return The color held by this {@link #FORMAT_COLOR} SliceItem
*/
public int getColor() {
return (Integer) mObj;
}
/**
- * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem
+ * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
*/
public Slice getSlice() {
- if (getType() == TYPE_ACTION) {
+ if (FORMAT_ACTION.equals(getFormat())) {
return ((Pair<PendingIntent, Slice>) mObj).second;
}
return (Slice) mObj;
}
/**
- * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem
+ * @return The timestamp held by this {@link #FORMAT_TIMESTAMP} SliceItem
*/
public long getTimestamp() {
return (Long) mObj;
@@ -217,8 +242,9 @@ public final class SliceItem implements Parcelable {
*/
public SliceItem(Parcel in) {
mHints = in.readStringArray();
- mType = in.readInt();
- mObj = readObj(mType, in);
+ mFormat = in.readString();
+ mSubType = in.readString();
+ mObj = readObj(mFormat, in);
}
@Override
@@ -229,8 +255,9 @@ public final class SliceItem implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStringArray(mHints);
- dest.writeInt(mType);
- writeObj(dest, flags, mObj, mType);
+ dest.writeString(mFormat);
+ dest.writeString(mSubType);
+ writeObj(dest, flags, mObj, mFormat);
}
/**
@@ -259,49 +286,54 @@ public final class SliceItem implements Parcelable {
return false;
}
- private void writeObj(Parcel dest, int flags, Object obj, int type) {
- switch (type) {
- case TYPE_SLICE:
- case TYPE_REMOTE_VIEW:
- case TYPE_IMAGE:
- case TYPE_REMOTE_INPUT:
+ private static String getBaseType(String type) {
+ int index = type.indexOf('/');
+ if (index >= 0) {
+ return type.substring(0, index);
+ }
+ return type;
+ }
+
+ private static void writeObj(Parcel dest, int flags, Object obj, String type) {
+ switch (getBaseType(type)) {
+ case FORMAT_SLICE:
+ case FORMAT_IMAGE:
+ case FORMAT_REMOTE_INPUT:
((Parcelable) obj).writeToParcel(dest, flags);
break;
- case TYPE_ACTION:
+ case FORMAT_ACTION:
((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
break;
- case TYPE_TEXT:
- TextUtils.writeToParcel((CharSequence) mObj, dest, flags);
+ case FORMAT_TEXT:
+ TextUtils.writeToParcel((CharSequence) obj, dest, flags);
break;
- case TYPE_COLOR:
- dest.writeInt((Integer) mObj);
+ case FORMAT_COLOR:
+ dest.writeInt((Integer) obj);
break;
- case TYPE_TIMESTAMP:
- dest.writeLong((Long) mObj);
+ case FORMAT_TIMESTAMP:
+ dest.writeLong((Long) obj);
break;
}
}
- private static Object readObj(int type, Parcel in) {
- switch (type) {
- case TYPE_SLICE:
+ private static Object readObj(String type, Parcel in) {
+ switch (getBaseType(type)) {
+ case FORMAT_SLICE:
return Slice.CREATOR.createFromParcel(in);
- case TYPE_TEXT:
+ case FORMAT_TEXT:
return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- case TYPE_IMAGE:
+ case FORMAT_IMAGE:
return Icon.CREATOR.createFromParcel(in);
- case TYPE_ACTION:
- return new Pair<PendingIntent, Slice>(
+ case FORMAT_ACTION:
+ return new Pair<>(
PendingIntent.CREATOR.createFromParcel(in),
Slice.CREATOR.createFromParcel(in));
- case TYPE_REMOTE_VIEW:
- return RemoteViews.CREATOR.createFromParcel(in);
- case TYPE_COLOR:
+ case FORMAT_COLOR:
return in.readInt();
- case TYPE_TIMESTAMP:
+ case FORMAT_TIMESTAMP:
return in.readLong();
- case TYPE_REMOTE_INPUT:
+ case FORMAT_REMOTE_INPUT:
return RemoteInput.CREATOR.createFromParcel(in);
}
throw new RuntimeException("Unsupported type " + type);
@@ -318,29 +350,4 @@ public final class SliceItem implements Parcelable {
return new SliceItem[size];
}
};
-
- /**
- * @hide
- */
- public static String typeToString(int type) {
- switch (type) {
- case TYPE_SLICE:
- return "Slice";
- case TYPE_TEXT:
- return "Text";
- case TYPE_IMAGE:
- return "Image";
- case TYPE_ACTION:
- return "Action";
- case TYPE_REMOTE_VIEW:
- return "RemoteView";
- case TYPE_COLOR:
- return "Color";
- case TYPE_TIMESTAMP:
- return "Timestamp";
- case TYPE_REMOTE_INPUT:
- return "RemoteInput";
- }
- return "Unrecognized type: " + type;
- }
}
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 05f4ce6e..ac5365c3 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -17,7 +17,6 @@ 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;
@@ -37,6 +36,7 @@ import android.os.StrictMode.ThreadPolicy;
import android.os.UserHandle;
import android.util.Log;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
@@ -93,6 +93,10 @@ public abstract class SliceProvider extends ContentProvider {
/**
* @hide
*/
+ public static final String EXTRA_SUPPORTED_SPECS = "supported_specs";
+ /**
+ * @hide
+ */
public static final String METHOD_SLICE = "bind_slice";
/**
* @hide
@@ -118,12 +122,25 @@ public abstract class SliceProvider extends ContentProvider {
* off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
* when the app is ready to provide the complete data in onBindSlice.
* <p>
+ * The slice returned should have a spec that is compatible with one of
+ * the supported specs.
*
+ * @param sliceUri Uri to bind.
+ * @param supportedSpecs List of supported specs.
* @see {@link Slice}.
* @see {@link Slice#HINT_PARTIAL}
*/
- // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
- public abstract Slice onBindSlice(Uri sliceUri);
+ public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
+ return onBindSlice(sliceUri);
+ }
+
+ /**
+ * @deprecated migrating to {@link #onBindSlice(Uri, List)}
+ */
+ @Deprecated
+ public Slice onBindSlice(Uri sliceUri) {
+ return null;
+ }
/**
* This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
@@ -132,7 +149,6 @@ public abstract class SliceProvider extends ContentProvider {
*
* @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(
@@ -195,8 +211,9 @@ public abstract class SliceProvider extends ContentProvider {
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
"Slice binding requires the permission BIND_SLICE");
}
+ List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
- Slice s = handleBindSlice(uri);
+ Slice s = handleBindSlice(uri, supportedSpecs);
Bundle b = new Bundle();
b.putParcelable(EXTRA_SLICE, s);
return b;
@@ -205,9 +222,10 @@ public abstract class SliceProvider extends ContentProvider {
"Slice binding requires the permission BIND_SLICE");
Intent intent = extras.getParcelable(EXTRA_INTENT);
Uri uri = onMapIntentToUri(intent);
+ List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
Bundle b = new Bundle();
if (uri != null) {
- Slice s = handleBindSlice(uri);
+ Slice s = handleBindSlice(uri, supportedSpecs);
b.putParcelable(EXTRA_SLICE, s);
} else {
b.putParcelable(EXTRA_SLICE, null);
@@ -217,14 +235,14 @@ public abstract class SliceProvider extends ContentProvider {
return super.call(method, arg, extras);
}
- private Slice handleBindSlice(Uri sliceUri) {
+ private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
if (Looper.myLooper() == Looper.getMainLooper()) {
- return onBindSliceStrict(sliceUri);
+ return onBindSliceStrict(sliceUri, supportedSpecs);
} else {
CountDownLatch latch = new CountDownLatch(1);
Slice[] output = new Slice[1];
Handler.getMain().post(() -> {
- output[0] = onBindSliceStrict(sliceUri);
+ output[0] = onBindSliceStrict(sliceUri, supportedSpecs);
latch.countDown();
});
try {
@@ -236,14 +254,14 @@ public abstract class SliceProvider extends ContentProvider {
}
}
- private Slice onBindSliceStrict(Uri sliceUri) {
+ private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) {
ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyDeath()
.build());
- return onBindSlice(sliceUri);
+ return onBindSlice(sliceUri, supportedSpecs);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
diff --git a/android/app/slice/SliceQuery.java b/android/app/slice/SliceQuery.java
index 9943c492..20eca880 100644
--- a/android/app/slice/SliceQuery.java
+++ b/android/app/slice/SliceQuery.java
@@ -19,6 +19,7 @@ package android.app.slice;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.Spliterators;
import java.util.stream.Collectors;
@@ -37,14 +38,15 @@ public class SliceQuery {
*/
public static SliceItem getPrimaryIcon(Slice slice) {
for (SliceItem item : slice.getItems()) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
+ if (Objects.equals(item.getFormat(), SliceItem.FORMAT_IMAGE)) {
return item;
}
- if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ if (!(compareTypes(item, SliceItem.FORMAT_SLICE)
+ && item.hasHint(Slice.HINT_LIST))
&& !item.hasHint(Slice.HINT_ACTIONS)
&& !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != SliceItem.TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+ && !compareTypes(item, SliceItem.FORMAT_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, SliceItem.FORMAT_IMAGE);
if (icon != null) {
return icon;
}
@@ -78,23 +80,23 @@ public class SliceQuery {
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type) {
+ public static List<SliceItem> findAll(SliceItem s, String type) {
return findAll(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+ public static List<SliceItem> findAll(SliceItem s, String type, String hints, String nonHints) {
return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+ public static List<SliceItem> findAll(SliceItem s, String type, String[] hints,
String[] nonHints) {
- return stream(s).filter(item -> (type == -1 || item.getType() == type)
+ return stream(s).filter(item -> compareTypes(item, type)
&& (item.hasHints(hints) && !item.hasAnyHints(nonHints)))
.collect(Collectors.toList());
}
@@ -102,45 +104,45 @@ public class SliceQuery {
/**
* @hide
*/
- public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+ public static SliceItem find(Slice s, String type, String hints, String nonHints) {
return find(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static SliceItem find(Slice s, int type) {
+ public static SliceItem find(Slice s, String type) {
return find(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type) {
+ public static SliceItem find(SliceItem s, String type) {
return find(s, type, (String[]) null, null);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+ public static SliceItem find(SliceItem s, String type, String hints, String nonHints) {
return find(s, type, new String[]{ hints }, new String[]{ nonHints });
}
/**
* @hide
*/
- public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
+ public static SliceItem find(Slice s, String type, String[] hints, String[] nonHints) {
List<String> h = s.getHints();
- return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
- hints, nonHints);
+ return find(new SliceItem(s, SliceItem.FORMAT_SLICE, null, h.toArray(new String[h.size()])),
+ type, hints, nonHints);
}
/**
* @hide
*/
- public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
- return stream(s).filter(item -> (item.getType() == type || type == -1)
+ public static SliceItem find(SliceItem s, String type, String[] hints, String[] nonHints) {
+ return stream(s).filter(item -> compareTypes(item, type)
&& (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null);
}
@@ -159,8 +161,8 @@ public class SliceQuery {
@Override
public SliceItem next() {
SliceItem item = items.poll();
- if (item.getType() == SliceItem.TYPE_SLICE
- || item.getType() == SliceItem.TYPE_ACTION) {
+ if (compareTypes(item, SliceItem.FORMAT_SLICE)
+ || compareTypes(item, SliceItem.FORMAT_ACTION)) {
items.addAll(item.getSlice().getItems());
}
return item;
@@ -168,4 +170,19 @@ public class SliceQuery {
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
}
+
+ /**
+ * @hide
+ */
+ public static boolean compareTypes(SliceItem item, String desiredType) {
+ final int typeLength = desiredType.length();
+ if (typeLength == 3 && desiredType.equals("*/*")) {
+ return true;
+ }
+ if (item.getSubType() == null && desiredType.indexOf('/') < 0) {
+ return item.getFormat().equals(desiredType);
+ }
+ return (item.getFormat() + "/" + item.getSubType())
+ .matches(desiredType.replaceAll("\\*", ".*"));
+ }
}
diff --git a/android/app/slice/SliceSpec.java b/android/app/slice/SliceSpec.java
new file mode 100644
index 00000000..433b67e9
--- /dev/null
+++ b/android/app/slice/SliceSpec.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.slice;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class describing the structure of the data contained within a slice.
+ * <p>
+ * A data version contains a string which describes the type of structure
+ * and a revision which denotes this specific implementation. Revisions are expected
+ * to be backwards compatible and monotonically increasing. Meaning if a
+ * SliceSpec has the same type and an equal or lesser revision,
+ * it is expected to be compatible.
+ * <p>
+ * Apps rendering slices will provide a list of supported versions to the OS which
+ * will also be given to the app. Apps should only return a {@link Slice} with a
+ * {@link SliceSpec} that one of the supported {@link SliceSpec}s provided
+ * {@link #canRender}.
+ *
+ * @see Slice
+ * @see SliceProvider#onBindSlice(Uri)
+ */
+public final class SliceSpec implements Parcelable {
+
+ private final String mType;
+ private final int mRevision;
+
+ public SliceSpec(@NonNull String type, int revision) {
+ mType = type;
+ mRevision = revision;
+ }
+
+ /**
+ * @hide
+ */
+ public SliceSpec(Parcel source) {
+ mType = source.readString();
+ mRevision = source.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mType);
+ dest.writeInt(mRevision);
+ }
+
+ /**
+ * Gets the type of the version.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Gets the revision of the version.
+ */
+ public int getRevision() {
+ return mRevision;
+ }
+
+ /**
+ * Indicates that this spec can be used to render the specified spec.
+ * <p>
+ * Rendering support is not bi-directional (e.g. Spec v3 can render
+ * Spec v2, but Spec v2 cannot render Spec v3).
+ *
+ * @param candidate candidate format of data.
+ * @return true if versions are compatible.
+ * @see androidx.app.slice.widget.SliceView
+ */
+ public boolean canRender(@NonNull SliceSpec candidate) {
+ if (!mType.equals(candidate.mType)) return false;
+ return mRevision >= candidate.mRevision;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SliceSpec)) return false;
+ SliceSpec other = (SliceSpec) obj;
+ return mType.equals(other.mType) && mRevision == other.mRevision;
+ }
+
+ public static final Creator<SliceSpec> CREATOR = new Creator<SliceSpec>() {
+ @Override
+ public SliceSpec createFromParcel(Parcel source) {
+ return new SliceSpec(source);
+ }
+
+ @Override
+ public SliceSpec[] newArray(int size) {
+ return new SliceSpec[size];
+ }
+ };
+}
diff --git a/android/app/slice/widget/ActionRow.java b/android/app/slice/widget/ActionRow.java
deleted file mode 100644
index c96e6304..00000000
--- a/android/app/slice/widget/ActionRow.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteInput;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.drawable.Icon;
-import android.os.AsyncTask;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewParent;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * @hide
- */
-public class ActionRow extends FrameLayout {
-
- private static final int MAX_ACTIONS = 5;
- private final int mSize;
- private final int mIconPadding;
- private final LinearLayout mActionsGroup;
- private final boolean mFullActions;
- private int mColor = Color.BLACK;
-
- public ActionRow(Context context, boolean fullActions) {
- super(context);
- mFullActions = fullActions;
- mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
- context.getResources().getDisplayMetrics());
- mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
- context.getResources().getDisplayMetrics());
- mActionsGroup = new LinearLayout(context);
- mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
- mActionsGroup.setLayoutParams(
- new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- addView(mActionsGroup);
- }
-
- private void setColor(int color) {
- mColor = color;
- for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
- View view = mActionsGroup.getChildAt(i);
- SliceItem item = (SliceItem) view.getTag();
- boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
- if (tint) {
- ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
- }
- }
- }
-
- private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
- ImageView imageView = new ImageView(getContext());
- imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
- imageView.setScaleType(ScaleType.FIT_CENTER);
- imageView.setImageIcon(icon);
- if (allowTint) {
- imageView.setImageTintList(ColorStateList.valueOf(mColor));
- }
- imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
- android.R.attr.selectableItemBackground));
- imageView.setTag(image);
- addAction(imageView);
- return imageView;
- }
-
- /**
- * Set the actions and color for this action row.
- */
- public void setActions(SliceItem actionRow, SliceItem defColor) {
- removeAllViews();
- mActionsGroup.removeAllViews();
- addView(mActionsGroup);
-
- SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
- if (color == null) {
- color = defColor;
- }
- if (color != null) {
- setColor(color.getColor());
- }
- SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
- if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
- return;
- }
- SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
- if (image == null) {
- return;
- }
- boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
- SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
- if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
- addAction(image.getIcon(), tint, image).setOnClickListener(
- v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
- createRemoteInputView(mColor, getContext());
- } else {
- addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
- () -> {
- try {
- action.getAction().send();
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }));
- }
- });
- setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
- }
-
- private void addAction(View child) {
- mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
- }
-
- private void createRemoteInputView(int color, Context context) {
- View riv = RemoteInputView.inflate(context, this);
- riv.setVisibility(View.INVISIBLE);
- addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- riv.setBackgroundColor(color);
- }
-
- private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
- RemoteInput input) {
- if (input == null) {
- return false;
- }
-
- ViewParent p = view.getParent().getParent();
- RemoteInputView riv = null;
- while (p != null) {
- if (p instanceof View) {
- View pv = (View) p;
- riv = findRemoteInputView(pv);
- if (riv != null) {
- break;
- }
- }
- p = p.getParent();
- }
- if (riv == null) {
- return false;
- }
-
- int width = view.getWidth();
- if (view instanceof TextView) {
- // Center the reveal on the text which might be off-center from the TextView
- TextView tv = (TextView) view;
- if (tv.getLayout() != null) {
- int innerWidth = (int) tv.getLayout().getLineWidth(0);
- innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
- width = Math.min(width, innerWidth);
- }
- }
- int cx = view.getLeft() + width / 2;
- int cy = view.getTop() + view.getHeight() / 2;
- int w = riv.getWidth();
- int h = riv.getHeight();
- int r = Math.max(
- Math.max(cx + cy, cx + (h - cy)),
- Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
- riv.setRevealParameters(cx, cy, r);
- riv.setPendingIntent(pendingIntent);
- riv.setRemoteInput(new RemoteInput[] {
- input
- }, input);
- riv.focusAnimated();
- return true;
- }
-
- private RemoteInputView findRemoteInputView(View v) {
- if (v == null) {
- return null;
- }
- return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
- }
-}
diff --git a/android/app/slice/widget/GridView.java b/android/app/slice/widget/GridView.java
deleted file mode 100644
index 793abc05..00000000
--- a/android/app/slice/widget/GridView.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.widget.LargeSliceAdapter.SliceListView;
-import android.content.Context;
-import android.graphics.Color;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public class GridView extends LinearLayout implements SliceListView {
-
- private static final String TAG = "GridView";
-
- private static final int MAX_IMAGES = 3;
- private static final int MAX_ALL = 5;
- private boolean mIsAllImages;
-
- public GridView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mIsAllImages) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = width / getChildCount();
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,
- height);
- getLayoutParams().height = height;
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).getLayoutParams().height = height;
- }
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- mIsAllImages = true;
- removeAllViews();
- int total = 1;
- if (slice.getType() == SliceItem.TYPE_SLICE) {
- List<SliceItem> items = slice.getSlice().getItems();
- total = items.size();
- for (int i = 0; i < total; i++) {
- SliceItem item = items.get(i);
- if (isFull()) {
- continue;
- }
- if (!addItem(item)) {
- mIsAllImages = false;
- }
- }
- } else {
- if (!isFull()) {
- if (!addItem(slice)) {
- mIsAllImages = false;
- }
- }
- }
- if (total > getChildCount() && mIsAllImages) {
- addExtraCount(total - getChildCount());
- }
- }
-
- private void addExtraCount(int numExtra) {
- View last = getChildAt(getChildCount() - 1);
- FrameLayout frame = new FrameLayout(getContext());
- frame.setLayoutParams(last.getLayoutParams());
-
- removeView(last);
- frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
- TextView v = new TextView(getContext());
- v.setTextColor(Color.WHITE);
- v.setBackgroundColor(0x4d000000);
- v.setText(getResources().getString(R.string.slice_more_content, numExtra));
- v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
- v.setGravity(Gravity.CENTER);
- frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
- addView(frame);
- }
-
- private boolean isFull() {
- return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
- }
-
- /**
- * 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());
- v.setScaleType(ScaleType.CENTER_CROP);
- addView(v, new LayoutParams(0, MATCH_PARENT, 1));
- return true;
- } else {
- LinearLayout v = new LinearLayout(getContext());
- int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 12, getContext().getResources().getDisplayMetrics());
- v.setPadding(0, s, 0, 0);
- v.setOrientation(LinearLayout.VERTICAL);
- v.setGravity(Gravity.CENTER_HORIZONTAL);
- // TODO: Unify sporadic inflates that happen throughout the code.
- ArrayList<SliceItem> items = new ArrayList<>();
- if (item.getType() == SliceItem.TYPE_SLICE) {
- 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:
- boolean title = false;
- if ((item.hasAnyHints(new String[] {
- Slice.HINT_LARGE, Slice.HINT_TITLE
- }))) {
- title = true;
- }
- TextView tv = (TextView) LayoutInflater.from(context).inflate(
- title ? R.layout.slice_title : R.layout.slice_secondary_text, null);
- tv.setText(i.getText());
- v.addView(tv);
- break;
- case SliceItem.TYPE_IMAGE:
- ImageView iv = new ImageView(context);
- iv.setImageIcon(i.getIcon());
- if (item.hasHint(Slice.HINT_LARGE)) {
- iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- } else {
- int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 48, context.getResources().getDisplayMetrics());
- iv.setLayoutParams(new LayoutParams(size, size));
- }
- v.addView(iv);
- break;
- case SliceItem.TYPE_REMOTE_VIEW:
- v.addView(i.getRemoteView().apply(context, v));
- break;
- case SliceItem.TYPE_COLOR:
- // TODO: Support color to tint stuff here.
- break;
- }
- });
- addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
- return false;
- }
- }
-}
diff --git a/android/app/slice/widget/LargeSliceAdapter.java b/android/app/slice/widget/LargeSliceAdapter.java
deleted file mode 100644
index 267fff6a..00000000
--- a/android/app/slice/widget/LargeSliceAdapter.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceViewHolder;
-import android.content.Context;
-import android.util.ArrayMap;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
-
-import com.android.internal.R;
-import com.android.internal.widget.RecyclerView;
-import com.android.internal.widget.RecyclerView.ViewHolder;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * @hide
- */
-public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
-
- public static final int TYPE_DEFAULT = 1;
- public static final int TYPE_HEADER = 2;
- public static final int TYPE_GRID = 3;
- public static final int TYPE_MESSAGE = 4;
- public static final int TYPE_MESSAGE_LOCAL = 5;
- public static final int TYPE_REMOTE_VIEWS = 6;
-
- private final IdGenerator mIdGen = new IdGenerator();
- private final Context mContext;
- private List<SliceWrapper> mSlices = new ArrayList<>();
- private SliceItem mColor;
-
- public LargeSliceAdapter(Context context) {
- mContext = context;
- setHasStableIds(true);
- }
-
- /**
- * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
- */
- public void setSliceItems(List<SliceItem> slices, SliceItem color) {
- mColor = color;
- mIdGen.resetUsage();
- mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
- .collect(Collectors.toList());
- notifyDataSetChanged();
- }
-
- @Override
- public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = inflateForType(viewType);
- v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- return new SliceViewHolder(v);
- }
-
- @Override
- public int getItemViewType(int position) {
- return mSlices.get(position).mType;
- }
-
- @Override
- public long getItemId(int position) {
- return mSlices.get(position).mId;
- }
-
- @Override
- public int getItemCount() {
- return mSlices.size();
- }
-
- @Override
- public void onBindViewHolder(SliceViewHolder holder, int position) {
- SliceWrapper slice = mSlices.get(position);
- if (holder.mSliceView != null) {
- holder.mSliceView.setColor(mColor);
- holder.mSliceView.setSliceItem(slice.mItem);
- } else if (slice.mType == TYPE_REMOTE_VIEWS) {
- FrameLayout frame = (FrameLayout) holder.itemView;
- frame.removeAllViews();
- frame.addView(slice.mItem.getRemoteView().apply(mContext, frame));
- }
- }
-
- private View inflateForType(int viewType) {
- switch (viewType) {
- case TYPE_REMOTE_VIEWS:
- return new FrameLayout(mContext);
- case TYPE_GRID:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_grid, null);
- case TYPE_MESSAGE:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_message, null);
- case TYPE_MESSAGE_LOCAL:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_message_local, null);
- }
- return new SmallTemplateView(mContext);
- }
-
- protected static class SliceWrapper {
- private final SliceItem mItem;
- private final int mType;
- private final long mId;
-
- public SliceWrapper(SliceItem item, IdGenerator idGen) {
- mItem = item;
- mType = getType(item);
- mId = idGen.getId(item);
- }
-
- public static int getType(SliceItem item) {
- if (item.getType() == SliceItem.TYPE_REMOTE_VIEW) {
- return TYPE_REMOTE_VIEWS;
- }
- if (item.hasHint(Slice.HINT_MESSAGE)) {
- // TODO: Better way to determine me or not? Something more like Messaging style.
- if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
- return TYPE_MESSAGE;
- } else {
- return TYPE_MESSAGE_LOCAL;
- }
- }
- if (item.hasHint(Slice.HINT_HORIZONTAL)) {
- return TYPE_GRID;
- }
- return TYPE_DEFAULT;
- }
- }
-
- /**
- * A {@link ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
- */
- public static class SliceViewHolder extends ViewHolder {
- public final SliceListView mSliceView;
-
- public SliceViewHolder(View itemView) {
- super(itemView);
- mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
- }
- }
-
- /**
- * View slices being displayed in {@link LargeSliceAdapter}.
- */
- public interface SliceListView {
- /**
- * Set the slice item for this view.
- */
- void setSliceItem(SliceItem slice);
-
- /**
- * Set the color for the items in this view.
- */
- default void setColor(SliceItem color) {
-
- }
- }
-
- private static class IdGenerator {
- private long mNextLong = 0;
- private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
- private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
-
- public long getId(SliceItem item) {
- String str = genString(item);
- if (!mCurrentIds.containsKey(str)) {
- mCurrentIds.put(str, mNextLong++);
- }
- long id = mCurrentIds.get(str);
- int index = mUsedIds.getOrDefault(str, 0);
- mUsedIds.put(str, index + 1);
- return id + index * 10000;
- }
-
- private String genString(SliceItem item) {
- StringBuilder builder = new StringBuilder();
- SliceQuery.stream(item).forEach(i -> {
- builder.append(i.getType());
- i.removeHint(Slice.HINT_SELECTED);
- builder.append(i.getHints());
- switch (i.getType()) {
- case SliceItem.TYPE_REMOTE_VIEW:
- builder.append(i.getRemoteView());
- break;
- case SliceItem.TYPE_IMAGE:
- builder.append(i.getIcon());
- break;
- case SliceItem.TYPE_TEXT:
- builder.append(i.getText());
- break;
- case SliceItem.TYPE_COLOR:
- builder.append(i.getColor());
- break;
- }
- });
- return builder.toString();
- }
-
- public void resetUsage() {
- mUsedIds.clear();
- }
- }
-}
diff --git a/android/app/slice/widget/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java
deleted file mode 100644
index 788f6fb6..00000000
--- a/android/app/slice/widget/LargeTemplateView.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.SliceView.SliceModeView;
-import android.content.Context;
-import android.util.TypedValue;
-
-import com.android.internal.widget.LinearLayoutManager;
-import com.android.internal.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public class LargeTemplateView extends SliceModeView {
-
- private final LargeSliceAdapter mAdapter;
- private final RecyclerView mRecyclerView;
- private final int mDefaultHeight;
- private final int mMaxHeight;
- private Slice mSlice;
- private boolean mIsScrollable;
-
- public LargeTemplateView(Context context) {
- super(context);
-
- mRecyclerView = new RecyclerView(getContext());
- mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mAdapter = new LargeSliceAdapter(context);
- mRecyclerView.setAdapter(mAdapter);
- addView(mRecyclerView);
- mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
- getResources().getDisplayMetrics());
- mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
- getResources().getDisplayMetrics());
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_LARGE;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mRecyclerView.getMeasuredHeight() > mMaxHeight
- || (mSlice != null && mSlice.hasHint(Slice.HINT_PARTIAL))) {
- mRecyclerView.getLayoutParams().height = mDefaultHeight;
- } else {
- mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public void setSlice(Slice slice) {
- SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- mSlice = slice;
- List<SliceItem> items = new ArrayList<>();
- boolean[] hasHeader = new boolean[1];
- if (slice.hasHint(Slice.HINT_LIST)) {
- addList(slice, items);
- } else {
- slice.getItems().forEach(item -> {
- 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)) {
- addList(item.getSlice(), items);
- } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
- items.add(item);
- } else if (!hasHeader[0]) {
- hasHeader[0] = true;
- items.add(0, item);
- } else {
- item.addHint(Slice.HINT_LIST_ITEM);
- items.add(item);
- }
- });
- }
- mAdapter.setSliceItems(items, color);
- }
-
- private void addList(Slice slice, List<SliceItem> items) {
- List<SliceItem> sliceItems = slice.getItems();
- sliceItems.forEach(i -> {
- if (!i.hasHint(Slice.HINT_HIDDEN) && i.getType() != SliceItem.TYPE_COLOR) {
- i.addHint(Slice.HINT_LIST_ITEM);
- items.add(i);
- }
- });
- }
-
- /**
- * Whether or not the content in this template should be scrollable.
- */
- public void setScrollable(boolean isScrollable) {
- // TODO -- restrict / enable how much this view can show
- mIsScrollable = isScrollable;
- }
-}
diff --git a/android/app/slice/widget/MessageView.java b/android/app/slice/widget/MessageView.java
deleted file mode 100644
index 3124398e..00000000
--- a/android/app/slice/widget/MessageView.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceListView;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.text.SpannableStringBuilder;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * @hide
- */
-public class MessageView extends LinearLayout implements SliceListView {
-
- private TextView mDetails;
- private ImageView mIcon;
-
- public MessageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mDetails = findViewById(android.R.id.summary);
- mIcon = findViewById(android.R.id.icon);
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
- if (source != null) {
- final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 24, getContext().getResources().getDisplayMetrics());
- // TODO try and turn this into a drawable
- Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
- Canvas iconCanvas = new Canvas(iconBm);
- Drawable d = source.getIcon().loadDrawable(getContext());
- d.setBounds(0, 0, iconSize, iconSize);
- d.draw(iconCanvas);
- mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
- }
- SpannableStringBuilder builder = new SpannableStringBuilder();
- SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
- if (builder.length() != 0) {
- builder.append('\n');
- }
- builder.append(text.getText());
- });
- mDetails.setText(builder.toString());
- }
-
-}
diff --git a/android/app/slice/widget/RemoteInputView.java b/android/app/slice/widget/RemoteInputView.java
deleted file mode 100644
index 6eff5afb..00000000
--- a/android/app/slice/widget/RemoteInputView.java
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import android.animation.Animator;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutManager;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.R;
-
-/**
- * Host for the remote input.
- *
- * @hide
- */
-// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
-public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
-
- private static final String TAG = "RemoteInput";
-
- /**
- * A marker object that let's us easily find views of this class.
- */
- public static final Object VIEW_TAG = new Object();
-
- private RemoteEditText mEditText;
- private ImageButton mSendButton;
- private ProgressBar mProgressBar;
- private PendingIntent mPendingIntent;
- private RemoteInput[] mRemoteInputs;
- private RemoteInput mRemoteInput;
-
- private int mRevealCx;
- private int mRevealCy;
- private int mRevealR;
- private boolean mResetting;
-
- public RemoteInputView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mProgressBar = findViewById(R.id.remote_input_progress);
- mSendButton = findViewById(R.id.remote_input_send);
- mSendButton.setOnClickListener(this);
-
- mEditText = (RemoteEditText) getChildAt(0);
- mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- final boolean isSoftImeEvent = event == null
- && (actionId == EditorInfo.IME_ACTION_DONE
- || actionId == EditorInfo.IME_ACTION_NEXT
- || actionId == EditorInfo.IME_ACTION_SEND);
- final boolean isKeyboardEnterKey = event != null
- && KeyEvent.isConfirmKey(event.getKeyCode())
- && event.getAction() == KeyEvent.ACTION_DOWN;
-
- if (isSoftImeEvent || isKeyboardEnterKey) {
- if (mEditText.length() > 0) {
- sendRemoteInput();
- }
- // Consume action to prevent IME from closing.
- return true;
- }
- return false;
- }
- });
- mEditText.addTextChangedListener(this);
- mEditText.setInnerFocusable(false);
- mEditText.mRemoteInputView = this;
- }
-
- private void sendRemoteInput() {
- Bundle results = new Bundle();
- results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
- Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
- results);
-
- mEditText.setEnabled(false);
- mSendButton.setVisibility(INVISIBLE);
- mProgressBar.setVisibility(VISIBLE);
- mEditText.mShowImeOnInputConnection = false;
-
- // Tell ShortcutManager that this package has been "activated". ShortcutManager
- // will reset the throttling for this package.
- // Strictly speaking, the intent receiver may be different from the intent creator,
- // but that's an edge case, and also because we can't always know which package will receive
- // an intent, so we just reset for the creator.
- getContext().getSystemService(ShortcutManager.class).onApplicationActive(
- mPendingIntent.getCreatorPackage(),
- getContext().getUserId());
-
- try {
- mPendingIntent.send(mContext, 0, fillInIntent);
- reset();
- } catch (PendingIntent.CanceledException e) {
- Log.i(TAG, "Unable to send remote input result", e);
- Toast.makeText(mContext, "Failure sending pending intent for inline reply :(",
- Toast.LENGTH_SHORT).show();
- reset();
- }
- }
-
- /**
- * Creates a remote input view.
- */
- public static RemoteInputView inflate(Context context, ViewGroup root) {
- RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
- R.layout.slice_remote_input, root, false);
- v.setTag(VIEW_TAG);
- return v;
- }
-
- @Override
- public void onClick(View v) {
- if (v == mSendButton) {
- sendRemoteInput();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- super.onTouchEvent(event);
-
- // We never want for a touch to escape to an outer view or one we covered.
- return true;
- }
-
- private void onDefocus() {
- setVisibility(INVISIBLE);
- }
-
- /**
- * Set the pending intent for remote input.
- */
- public void setPendingIntent(PendingIntent pendingIntent) {
- mPendingIntent = pendingIntent;
- }
-
- /**
- * Set the remote inputs for this view.
- */
- public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
- mRemoteInputs = remoteInputs;
- mRemoteInput = remoteInput;
- mEditText.setHint(mRemoteInput.getLabel());
- }
-
- /**
- * Focuses the remote input view.
- */
- public void focusAnimated() {
- if (getVisibility() != VISIBLE) {
- Animator animator = ViewAnimationUtils.createCircularReveal(
- this, mRevealCx, mRevealCy, 0, mRevealR);
- animator.setDuration(200);
- animator.start();
- }
- focus();
- }
-
- private void focus() {
- setVisibility(VISIBLE);
- mEditText.setInnerFocusable(true);
- mEditText.mShowImeOnInputConnection = true;
- mEditText.setSelection(mEditText.getText().length());
- mEditText.requestFocus();
- updateSendButton();
- }
-
- private void reset() {
- mResetting = true;
-
- mEditText.getText().clear();
- mEditText.setEnabled(true);
- mSendButton.setVisibility(VISIBLE);
- mProgressBar.setVisibility(INVISIBLE);
- updateSendButton();
- onDefocus();
-
- mResetting = false;
- }
-
- @Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (mResetting && child == mEditText) {
- // Suppress text events if it happens during resetting. Ideally this would be
- // suppressed by the text view not being shown, but that doesn't work here because it
- // needs to stay visible for the animation.
- return false;
- }
- return super.onRequestSendAccessibilityEvent(child, event);
- }
-
- private void updateSendButton() {
- mSendButton.setEnabled(mEditText.getText().length() != 0);
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- updateSendButton();
- }
-
- /**
- * Tries to find an action that matches the current pending intent of this view and updates its
- * state to that of the found action
- *
- * @return true if a matching action was found, false otherwise
- */
- public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
- if (mPendingIntent == null || actions == null) {
- return false;
- }
- Intent current = mPendingIntent.getIntent();
- if (current == null) {
- return false;
- }
-
- for (Notification.Action a : actions) {
- RemoteInput[] inputs = a.getRemoteInputs();
- if (a.actionIntent == null || inputs == null) {
- continue;
- }
- Intent candidate = a.actionIntent.getIntent();
- if (!current.filterEquals(candidate)) {
- continue;
- }
-
- RemoteInput input = null;
- for (RemoteInput i : inputs) {
- if (i.getAllowFreeFormInput()) {
- input = i;
- }
- }
- if (input == null) {
- continue;
- }
- setPendingIntent(a.actionIntent);
- setRemoteInput(inputs, input);
- return true;
- }
- return false;
- }
-
- /**
- * @hide
- */
- public void setRevealParameters(int cx, int cy, int r) {
- mRevealCx = cx;
- mRevealCy = cy;
- mRevealR = r;
- }
-
- @Override
- public void dispatchStartTemporaryDetach() {
- super.dispatchStartTemporaryDetach();
- // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
- // won't lose IME focus.
- detachViewFromParent(mEditText);
- }
-
- @Override
- public void dispatchFinishTemporaryDetach() {
- if (isAttachedToWindow()) {
- attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
- } else {
- removeDetachedView(mEditText, false /* animate */);
- }
- super.dispatchFinishTemporaryDetach();
- }
-
- /**
- * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
- * whenever the user navigates away from it or it becomes invisible.
- */
- public static class RemoteEditText extends EditText {
-
- private final Drawable mBackground;
- private RemoteInputView mRemoteInputView;
- boolean mShowImeOnInputConnection;
-
- public RemoteEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- mBackground = getBackground();
- }
-
- private void defocusIfNeeded(boolean animate) {
- if (mRemoteInputView != null || isTemporarilyDetached()) {
- if (isTemporarilyDetached()) {
- // We might get reattached but then the other one of HUN / expanded might steal
- // our focus, so we'll need to save our text here.
- }
- return;
- }
- if (isFocusable() && isEnabled()) {
- setInnerFocusable(false);
- if (mRemoteInputView != null) {
- mRemoteInputView.onDefocus();
- }
- mShowImeOnInputConnection = false;
- }
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
-
- if (!isShown()) {
- defocusIfNeeded(false /* animate */);
- }
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- if (!focused) {
- defocusIfNeeded(true /* animate */);
- }
- }
-
- @Override
- public void getFocusedRect(Rect r) {
- super.getFocusedRect(r);
- r.top = mScrollY;
- r.bottom = mScrollY + (mBottom - mTop);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // Eat the DOWN event here to prevent any default behavior.
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- defocusIfNeeded(true /* animate */);
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
-
- if (mShowImeOnInputConnection && inputConnection != null) {
- final InputMethodManager imm = InputMethodManager.getInstance();
- if (imm != null) {
- // onCreateInputConnection is called by InputMethodManager in the middle of
- // setting up the connection to the IME; wait with requesting the IME until that
- // work has completed.
- post(new Runnable() {
- @Override
- public void run() {
- imm.viewClicked(RemoteEditText.this);
- imm.showSoftInput(RemoteEditText.this, 0);
- }
- });
- }
- }
-
- return inputConnection;
- }
-
- @Override
- public void onCommitCompletion(CompletionInfo text) {
- clearComposingText();
- setText(text.getText());
- setSelection(getText().length());
- }
-
- void setInnerFocusable(boolean focusable) {
- setFocusableInTouchMode(focusable);
- setFocusable(focusable);
- setCursorVisible(focusable);
-
- if (focusable) {
- requestFocus();
- setBackground(mBackground);
- } else {
- setBackground(null);
- }
-
- }
- }
-}
diff --git a/android/app/slice/widget/ShortcutView.java b/android/app/slice/widget/ShortcutView.java
deleted file mode 100644
index 0b7ad0d6..00000000
--- a/android/app/slice/widget/ShortcutView.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-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
- */
-public class ShortcutView extends SliceModeView {
-
- private static final String TAG = "ShortcutView";
-
- private Uri mUri;
- private PendingIntent mAction;
- private SliceItem mLabel;
- private SliceItem mIcon;
-
- private int mLargeIconSize;
- private int mSmallIconSize;
-
- public ShortcutView(Context context) {
- super(context);
- 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();
- determineShortcutItems(getContext(), slice);
- SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- if (colorItem == null) {
- colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- }
- // TODO: pick better default colour
- final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
- ShapeDrawable circle = new ShapeDrawable(new OvalShape());
- circle.setTint(color);
- setBackground(circle);
- if (mIcon != null) {
- final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE);
- final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
- SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
- isLarge, this /* parent */);
- mUri = slice.getUri();
- setClickable(true);
- } else {
- setClickable(false);
- }
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_SHORTCUT;
- }
-
- @Override
- public boolean performClick() {
- if (!callOnClick()) {
- try {
- if (mAction != null) {
- mAction.send();
- } else {
- Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getContext().startActivity(intent);
- }
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }
- 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
deleted file mode 100644
index fa1b64ce..00000000
--- a/android/app/slice/widget/SliceView.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-
-/**
- * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
- * able to present slice content in a templated format outside of the associated app. The way this
- * content is displayed depends on the structure of the slice, the hints associated with the
- * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
- * <ul>
- * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
- * content or action associated with the slice.</li>
- * <li><b>Small</b>: The small format has a restricted height and can present a single
- * {@link SliceItem} or a limited collection of items.</li>
- * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
- * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
- * comfortably fit.</li>
- * </ul>
- * <p>
- * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
- * with some information on how the content should be displayed. For example, text annotated with
- * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
- * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
- * <p>
- * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
- * observer will be set for that uri and the view will update if there are any changes to the slice.
- * To use this the app must have a special permission to bind to the slice (see
- * {@link android.Manifest.permission#BIND_SLICE}).
- * <p>
- * Example usage:
- *
- * <pre class="prettyprint">
- * SliceView v = new SliceView(getContext());
- * v.setMode(desiredMode);
- * v.setSlice(sliceUri);
- * </pre>
- */
-public class SliceView extends ViewGroup {
-
- private static final String TAG = "SliceView";
-
- /**
- * @hide
- */
- public abstract static class SliceModeView extends FrameLayout {
-
- public SliceModeView(Context context) {
- super(context);
- }
-
- /**
- * @return the mode of the slice being presented.
- */
- public abstract String getMode();
-
- /**
- * @param slice the slice to show in this view.
- */
- public abstract void setSlice(Slice slice);
- }
-
- /**
- * @hide
- */
- @StringDef({
- MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
- })
- public @interface SliceMode {}
-
- /**
- * Mode indicating this slice should be presented in small template format.
- */
- public static final String MODE_SMALL = "SLICE_SMALL";
- /**
- * Mode indicating this slice should be presented in large template format.
- */
- public static final String MODE_LARGE = "SLICE_LARGE";
- /**
- * 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";
-
- /**
- * Will select the type of slice binding based on size of the View. TODO: Put in some info about
- * that selection.
- */
- private static final String MODE_AUTO = "auto";
-
- private String mMode = MODE_AUTO;
- private SliceModeView mCurrentView;
- private final ActionRow mActions;
- private Slice mCurrentSlice;
- private boolean mShowActions = true;
- private boolean mIsScrollable;
- private SliceObserver mObserver;
- private final int mShortcutSize;
-
- public SliceView(Context context) {
- this(context, null);
- }
-
- public SliceView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
- mActions = new ActionRow(mContext, true);
- mActions.setBackground(new ColorDrawable(0xffeeeeee));
- mCurrentView = new LargeTemplateView(mContext);
- addView(mCurrentView, getChildLp(mCurrentView));
- addView(mActions, getChildLp(mActions));
- mShortcutSize = getContext().getResources()
- .getDimensionPixelSize(R.dimen.slice_shortcut_size);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- measureChildren(widthMeasureSpec, heightMeasureSpec);
- int actionHeight = mActions.getVisibility() != View.GONE
- ? mActions.getMeasuredHeight()
- : 0;
- int newHeightSpec = MeasureSpec.makeMeasureSpec(
- mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
- int width = MeasureSpec.getSize(widthMeasureSpec);
- setMeasuredDimension(width, newHeightSpec);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- mCurrentView.layout(l, t, l + mCurrentView.getMeasuredWidth(),
- t + mCurrentView.getMeasuredHeight());
- if (mActions.getVisibility() != View.GONE) {
- mActions.layout(l, mCurrentView.getMeasuredHeight(), l + mActions.getMeasuredWidth(),
- mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
- }
- }
-
- /**
- * 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 boolean setSlice(@Nullable Intent intent) {
- Slice s = Slice.bindSlice(mContext, intent);
- if (s != null) {
- return setSlice(s.getUri());
- }
- return s != null;
- }
-
- /**
- * Populates this view with the {@link Slice} associated with the provided {@link Uri}. 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 when the slice identified by the provided URI 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 uri.
- */
- public boolean setSlice(@NonNull Uri sliceUri) {
- Preconditions.checkNotNull(sliceUri,
- "Uri cannot be null, to remove the slice use clearSlice()");
- if (sliceUri == null) {
- clearSlice();
- return false;
- }
- 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);
- }
- mCurrentSlice = s;
- reinflate();
- }
- return s != null;
- }
-
- /**
- * Populates this view to the provided {@link Slice}.
- * <p>
- * This does not register a content observer on the URI that the slice is backed by so it will
- * not update if the content changes. To have the view update when the content changes use
- * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
- * any special permissions.
- */
- public void showSlice(@NonNull Slice slice) {
- Preconditions.checkNotNull(slice,
- "Slice cannot be null, to remove the slice use clearSlice()");
- clearSlice();
- mCurrentSlice = slice;
- reinflate();
- }
-
- /**
- * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
- * done automatically during {@link #onDetachedFromWindow()}.
- * <p>
- * It is safe to call this method multiple times.
- */
- public void clearSlice() {
- mCurrentSlice = null;
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- mObserver = null;
- }
- }
-
- /**
- * Set the mode this view should present in.
- */
- public void setMode(@SliceMode String mode) {
- setMode(mode, false /* animate */);
- }
-
- /**
- * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
- */
- public void setScrollable(boolean isScrollable) {
- mIsScrollable = isScrollable;
- reinflate();
- }
-
- /**
- * @hide
- */
- public void setMode(@SliceMode String mode, boolean animate) {
- if (animate) {
- Log.e(TAG, "Animation not supported yet");
- }
- mMode = mode;
- reinflate();
- }
-
- /**
- * @return the mode this view is presenting in.
- */
- public @SliceMode String getMode() {
- if (mMode.equals(MODE_AUTO)) {
- return MODE_LARGE;
- }
- return mMode;
- }
-
- /**
- * @hide
- *
- * Whether this view should show a row of actions with it.
- */
- public void setShowActionRow(boolean show) {
- mShowActions = show;
- reinflate();
- }
-
- private SliceModeView createView(String mode) {
- switch (mode) {
- case MODE_SHORTCUT:
- return new ShortcutView(getContext());
- case MODE_SMALL:
- return new SmallTemplateView(getContext());
- }
- return new LargeTemplateView(getContext());
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- mObserver = null;
- }
- }
-
- private void registerSlice(Uri sliceUri) {
- if (sliceUri == null || mObserver == null) {
- return;
- }
- mContext.getContentResolver().registerContentObserver(sliceUri,
- false /* notifyForDescendants */, mObserver);
- }
-
- private void reinflate() {
- if (mCurrentSlice == null) {
- return;
- }
- // TODO: Smarter mapping here from one state to the next.
- SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
- List<SliceItem> items = mCurrentSlice.getItems();
- SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
- Slice.HINT_ACTIONS,
- Slice.HINT_ALT);
- String mode = getMode();
- if (!mode.equals(mCurrentView.getMode())) {
- removeAllViews();
- mCurrentView = createView(mode);
- addView(mCurrentView, getChildLp(mCurrentView));
- addView(mActions, getChildLp(mActions));
- }
- if (mode.equals(MODE_LARGE)) {
- ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
- }
- if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
- mCurrentView.setVisibility(View.VISIBLE);
- mCurrentView.setSlice(mCurrentSlice);
- } else {
- mCurrentView.setVisibility(View.GONE);
- }
-
- boolean showActions = mShowActions && actionRow != null
- && !mode.equals(MODE_SHORTCUT);
- if (showActions) {
- mActions.setActions(actionRow, color);
- mActions.setVisibility(View.VISIBLE);
- } else {
- mActions.setVisibility(View.GONE);
- }
- }
-
- private LayoutParams getChildLp(View child) {
- if (child instanceof ShortcutView) {
- return new LayoutParams(mShortcutSize, mShortcutSize);
- } else {
- return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
- }
- }
-
- private static void validate(Uri sliceUri) {
- if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- if (sliceUri.getPathSegments().size() == 0) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- }
-
- private class SliceObserver extends ContentObserver {
- SliceObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- this.onChange(selfChange, null);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- Slice s = Slice.bindSlice(mContext.getContentResolver(), uri);
- mCurrentSlice = s;
- reinflate();
- }
- }
-}
diff --git a/android/app/slice/widget/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java
deleted file mode 100644
index 1cf0055b..00000000
--- a/android/app/slice/widget/SliceViewUtil.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import android.annotation.ColorInt;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-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;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-/**
- * A bunch of utilities for slice UI.
- *
- * @hide
- */
-public class SliceViewUtil {
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorAccent(Context context) {
- return getColorAttr(context, android.R.attr.colorAccent);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorError(Context context) {
- return getColorAttr(context, android.R.attr.colorError);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getDefaultColor(Context context, int resId) {
- final ColorStateList list = context.getResources().getColorStateList(resId,
- context.getTheme());
-
- return list.getDefaultColor();
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getDisabled(Context context, int inputColor) {
- return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int applyAlphaAttr(Context context, int attr, int inputColor) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- float alpha = ta.getFloat(0, 0);
- ta.recycle();
- return applyAlpha(alpha, inputColor);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int applyAlpha(float alpha, int inputColor) {
- alpha *= Color.alpha(inputColor);
- return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
- Color.blue(inputColor));
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorAttr(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- @ColorInt
- int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- return colorAccent;
- }
-
- /**
- * @hide
- */
- public static int getThemeAttr(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- int theme = ta.getResourceId(0, 0);
- ta.recycle();
- return theme;
- }
-
- /**
- * @hide
- */
- public static Drawable getDrawable(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- Drawable drawable = ta.getDrawable(0);
- ta.recycle();
- return drawable;
- }
-
- /**
- * @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);
- v.setImageIcon(icon);
- parent.addView(v);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
- if (isLarge) {
- // XXX better way to convert from icon -> bitmap or crop an icon (?)
- Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
- Canvas iconCanvas = new Canvas(iconBm);
- v.layout(0, 0, iconSize, iconSize);
- v.draw(iconCanvas);
- v.setImageBitmap(getCircularBitmap(iconBm));
- } else {
- v.setColorFilter(Color.WHITE);
- }
- lp.width = iconSize;
- lp.height = iconSize;
- lp.gravity = Gravity.CENTER;
- }
-
- /**
- * @hide
- */
- public static Bitmap getCircularBitmap(Bitmap bitmap) {
- Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
- bitmap.getHeight(), Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- final Paint paint = new Paint();
- final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
- paint.setAntiAlias(true);
- canvas.drawARGB(0, 0, 0, 0);
- canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
- bitmap.getWidth() / 2, paint);
- paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
- canvas.drawBitmap(bitmap, rect, rect, paint);
- return output;
- }
-}
diff --git a/android/app/slice/widget/SmallTemplateView.java b/android/app/slice/widget/SmallTemplateView.java
deleted file mode 100644
index 1c4c5df2..00000000
--- a/android/app/slice/widget/SmallTemplateView.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.slice.widget;
-
-import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.widget.LargeSliceAdapter.SliceListView;
-import android.app.slice.widget.SliceView.SliceModeView;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.text.Format;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Small template is also used to construct list items for use with {@link LargeTemplateView}.
- *
- * @hide
- */
-public class SmallTemplateView extends SliceModeView implements SliceListView {
-
- private static final String TAG = "SmallTemplateView";
-
- private int mIconSize;
- private int mPadding;
-
- private LinearLayout mStartContainer;
- private TextView mTitleText;
- private TextView mSecondaryText;
- private LinearLayout mEndContainer;
-
- public SmallTemplateView(Context context) {
- super(context);
- inflate(context, R.layout.slice_small_template, this);
- mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
- mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.slice_padding);
-
- mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
- mTitleText = (TextView) findViewById(android.R.id.title);
- mSecondaryText = (TextView) findViewById(android.R.id.summary);
- mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_SMALL;
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- resetViews();
- SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- int color = colorItem != null ? colorItem.getColor() : -1;
-
- // Look for any title elements
- List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
- null);
- boolean hasTitleText = false;
- boolean hasTitleItem = false;
- for (int i = 0; i < titleItems.size(); i++) {
- SliceItem item = titleItems.get(i);
- if (!hasTitleItem) {
- // icon, action icon, or timestamp
- if (item.getType() == SliceItem.TYPE_ACTION) {
- hasTitleItem = addIcon(item, color, mStartContainer);
- } else if (item.getType() == SliceItem.TYPE_IMAGE) {
- addIcon(item, color, mStartContainer);
- hasTitleItem = true;
- } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
- TextView tv = new TextView(getContext());
- tv.setText(convertTimeToString(item.getTimestamp()));
- hasTitleItem = true;
- }
- }
- if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
- mTitleText.setText(item.getText());
- hasTitleText = true;
- }
- if (hasTitleText && hasTitleItem) {
- break;
- }
- }
- mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
- mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
-
- if (slice.getType() != SliceItem.TYPE_SLICE) {
- return;
- }
-
- // Deal with remaining items
- int itemCount = 0;
- boolean hasSummary = false;
- ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
- slice.getSlice().getItems());
- for (int i = 0; i < sliceItems.size(); i++) {
- SliceItem item = sliceItems.get(i);
- if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
- && !item.hasHint(Slice.HINT_TITLE)) {
- // TODO -- Should combine all text items?
- mSecondaryText.setText(item.getText());
- hasSummary = true;
- }
- if (itemCount <= 3) {
- if (item.getType() == SliceItem.TYPE_ACTION) {
- if (addIcon(item, color, mEndContainer)) {
- itemCount++;
- }
- } else if (item.getType() == SliceItem.TYPE_IMAGE) {
- addIcon(item, color, mEndContainer);
- itemCount++;
- } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
- TextView tv = new TextView(getContext());
- tv.setText(convertTimeToString(item.getTimestamp()));
- mEndContainer.addView(tv);
- itemCount++;
- } else if (item.getType() == SliceItem.TYPE_SLICE) {
- List<SliceItem> subItems = item.getSlice().getItems();
- for (int j = 0; j < subItems.size(); j++) {
- sliceItems.add(subItems.get(j));
- }
- }
- }
- }
- }
-
- @Override
- public void setSlice(Slice slice) {
- setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
- slice.getHints().toArray(new String[slice.getHints().size()])));
- }
-
- /**
- * @return Whether an icon was added.
- */
- private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
- SliceItem image = null;
- SliceItem action = null;
- if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
- image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
- action = sliceItem;
- } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
- image = sliceItem;
- }
- if (image != null) {
- ImageView iv = new ImageView(getContext());
- iv.setImageIcon(image.getIcon());
- if (action != null) {
- final SliceItem sliceAction = action;
- iv.setOnClickListener(v -> AsyncTask.execute(
- () -> {
- try {
- sliceAction.getAction().send();
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }));
- iv.setBackground(SliceViewUtil.getDrawable(getContext(),
- android.R.attr.selectableItemBackground));
- }
- if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
- iv.setColorFilter(color);
- }
- container.addView(iv);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
- lp.width = mIconSize;
- lp.height = mIconSize;
- lp.setMarginStart(mPadding);
- return true;
- }
- return false;
- }
-
- private String convertTimeToString(long time) {
- // TODO -- figure out what format(s) we support
- Date date = new Date(time);
- Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
- return format.format(date);
- }
-
- private void resetViews() {
- mStartContainer.removeAllViews();
- mEndContainer.removeAllViews();
- mTitleText.setText(null);
- mSecondaryText.setText(null);
- }
-}
diff --git a/android/app/usage/UsageEvents.java b/android/app/usage/UsageEvents.java
index 0d7a9413..8200414f 100644
--- a/android/app/usage/UsageEvents.java
+++ b/android/app/usage/UsageEvents.java
@@ -100,6 +100,12 @@ public final class UsageEvents implements Parcelable {
*/
public static final int CHOOSER_ACTION = 9;
+ /**
+ * An event type denoting that a notification was viewed by the user.
+ * @hide
+ */
+ public static final int NOTIFICATION_SEEN = 10;
+
/** @hide */
public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java
index 29e7439f..9954484f 100644
--- a/android/app/usage/UsageStatsManagerInternal.java
+++ b/android/app/usage/UsageStatsManagerInternal.java
@@ -16,6 +16,7 @@
package android.app.usage;
+import android.app.usage.AppStandby.StandbyBuckets;
import android.content.ComponentName;
import android.content.res.Configuration;
@@ -91,6 +92,19 @@ public abstract class UsageStatsManagerInternal {
public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
/**
+ * Returns the app standby bucket that the app is currently in. This accessor does
+ * <em>not</em> obfuscate instant apps.
+ *
+ * @param packageName
+ * @param userId
+ * @param nowElapsed The current time, in the elapsedRealtime time base
+ * @return the AppStandby bucket code the app currently resides in. If the app is
+ * unknown in the given user, STANDBY_BUCKET_NEVER is returned.
+ */
+ @StandbyBuckets public abstract int getAppStandbyBucket(String packageName, int userId,
+ long nowElapsed);
+
+ /**
* Returns all of the uids for a given user where all packages associating with that uid
* are in the app idle state -- there are no associated apps that are not idle. This means
* all of the returned uids can be safely considered app idle.