diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-11-30 18:18:21 -0500 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-11-30 18:18:21 -0500 |
commit | 4217cf85c20565a3446a662a7f07f26137b26b7f (patch) | |
tree | a0417b47a8cc802f6642f369fd2371165bec7b5c /android/app | |
parent | 6a65f2da209bff03cb0eb6da309710ac6ee5026d (diff) | |
download | android-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')
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. |